From 0214645ed9d3c4dfd42ca9d15762fac55b347d6e Mon Sep 17 00:00:00 2001 From: Ole Tange Date: Thu, 4 Feb 2010 00:54:06 +0100 Subject: [PATCH] parallel: Bug fix: If replace string contains regexp special char it should be escaped. Most xargs options implemented with unittest - though still undocumented --- parallel | 76 ++++++++++++++---------- parallel.1 | 11 ++-- unittest/actual-results/test15 | 100 ++++++++++++++++++++++++++++++++ unittest/tests-to-run/test15.sh | 68 ++++++++++++++++++++++ unittest/wanted-results/test15 | 100 ++++++++++++++++++++++++++++++++ 5 files changed, 319 insertions(+), 36 deletions(-) create mode 100644 unittest/actual-results/test15 create mode 100644 unittest/tests-to-run/test15.sh create mode 100644 unittest/wanted-results/test15 diff --git a/parallel b/parallel index fe68ab6f..77f4e606 100755 --- a/parallel +++ b/parallel @@ -133,7 +133,8 @@ output from different commands to be mixed. Can be reversed with B<-g>. =item B<-v> -Verbose. Print the job to be run on STDOUT. Can be reversed with B<-s>. +Verbose. Print the job to be run on STDOUT. Can be reversed with +B<--silent>. =item B<--xargs> =item B<-m> @@ -512,12 +513,12 @@ parallel --wait /tmp/comfile will wait until no more locks on the file Copyright (C) 2007-10-18 Ole Tange, http://ole.tange.dk -Copyright (C) 2008-2009 Ole Tange, http://ole.tange.dk +Copyright (C) 2008-2010 Ole Tange, http://ole.tange.dk =head1 LICENSE -Copyright (C) 2007-2009 Free Software Foundation, Inc. +Copyright (C) 2007-2010 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -535,7 +536,7 @@ along with this program. If not, see . =head1 DEPENDENCIES -B uses Perl, and the Perl modules Getopt::Std, IPC::Open3, +B uses Perl, and the Perl modules Getopt::Long, IPC::Open3, Symbol, IO::File, POSIX, and File::Temp. @@ -571,36 +572,35 @@ GetOptions("debug|D" => \$::opt_D, "quote|q" => \$::opt_q, "I=s" => \$::opt_I, "jobs|j=s" => \$::opt_P, - # xargs-compatability - implemented - unittest missing + # xargs-compatibility - implemented, man, unittest "max-procs|P=s" => \$::opt_P, + "delimiter|d=s" => \$::opt_d, + # xargs-compatibility - implemented, unittest - man missing "max-chars|s=i" => \$::opt_s, "arg-file|a=s" => \$::opt_a, - "delimiter|d=s" => \$::opt_d, "no-run-if-empty|r" => \$::opt_r, - ## echo " " | parallel -r echo - ## echo " " | parallel echo "replace|i:s" => \$::opt_i, "E=s" => \$::opt_E, "eof|e:s" => \$::opt_E, "max-args|n=i" => \$::opt_n, - ## (echo a b;echo c;echo d) | parallel -k -n1 -X echo - ## (echo a b;echo c;echo d) | parallel -k -n2 -X echo + "verbose|t" => \$::opt_t, + "help|h" => \$::opt_h, + "version" => \$::opt_version, + ## xargs-compatibility - implemented - unittest missing - man missing "interactive|p" => \$::opt_p, ## How to unittest? tty skal emuleres - "verbose|t" => \$::opt_t, # xargs-compatability - unimplemented - "help|h" => \$::opt_help, "L=i" => \$::opt_L, "max-lines|l:i" => \$::opt_l, - # (echo a b;echo c) | xargs -l1 echo - # (echo a b' ';echo c) | xargs -l1 echo - "version" => \$::opt_version, + ## (echo a b;echo c) | xargs -l1 echo + ## (echo a b' ';echo c) | xargs -l1 echo "show-limits" => \$::opt_show_limits, "exit|x" => \$::opt_x, ) || die_usage(); # Defaults: +$Global::version = 20100203; $Global::debug = 0; $Global::processes_to_run = 10; $command = undef; @@ -627,7 +627,7 @@ if(defined $::opt_u) { $Global::grouped = 0; } if(defined $::opt_c) { $Global::input_is_filename = 0; } if(defined $::opt_f) { $Global::input_is_filename = 1; } if(defined $::opt_0) { $/ = "\0"; } -if(defined $::opt_d) { $/ = $::opt_d; } +if(defined $::opt_d) { my $e="sprintf \"$::opt_d\""; $/ = eval $e; } if(defined $::opt_p) { $Global::interactive = $::opt_p; } if(defined $::opt_q) { $Global::quoting = 1; } if(defined $::opt_r) { $Global::ignore_empty = 1; } @@ -636,8 +636,10 @@ if(defined $::opt_I) { $Global::replacestring = $::opt_I; } if(defined $::opt_i and $::opt_i) { $Global::replacestring = $::opt_i; } if(defined $::opt_E and $::opt_E) { $Global::end_of_file_string = $::opt_E; } if(defined $::opt_n and $::opt_n) { $Global::max_number_of_args = $::opt_n; } +if(defined $::opt_h) { die_usage(); } +if(defined $::opt_version) { version(); exit(0); } -if(defined $::opt_a) { +if(defined $::opt_a) { if(not open(ARGFILE,"<".$::opt_a)) { print STDERR "parallel: Cannot open input file `$::opt_a': No such file or directory\n"; exit(-1); @@ -677,18 +679,18 @@ sub generate_command_line { my ($length_of_command_no_args); if($Global::xargs or $Global::Xargs) { # Count number of {}'s on the command line - $number_of_substitution = ($command =~ s/$Global::replacestring/$Global::replacestring/go); + $number_of_substitution = ($command =~ s/\Q$Global::replacestring\E/$Global::replacestring/go); $number_of_substitution ||= 1; } if($Global::xargs) { my $c = $command; # remove all {}s - $c =~ s/$Global::replacestring//go; + $c =~ s/\Q$Global::replacestring\E//go; $length_of_command_no_args = length($c); } if($Global::Xargs) { my $c = $command; - while($c =~ s/(\S*$Global::replacestring\S*)//o) { + while($c =~ s/(\S*\Q$Global::replacestring\E\S*)//o) { # Length of context minus the {} $length_of_context += length($1) - 2; } @@ -713,7 +715,7 @@ sub generate_command_line { if($quoted_args[0]) { last; } else { - die ("Command line too long at $next_arg"); + die ("Command line too long at: $next_arg"); } } if($Global::max_number_of_args and $number_of_args >= $Global::max_number_of_args) { @@ -723,17 +725,17 @@ sub generate_command_line { } if(@quoted_args) { $job_line = $command; - if(defined $job_line and $job_line =~/$Global::replacestring/o) { + if(defined $job_line and $job_line =~/\Q$Global::replacestring\E/o) { # substitute {} with args if($Global::Xargs) { # Context sensitive replace (foo{}bar with fooargsbar) - while($job_line =~/$Global::replacestring/o) { - $job_line =~ /(\S*$Global::replacestring\S*)/ or die ("This should never happen"); + while($job_line =~/\Q$Global::replacestring\E/o) { + $job_line =~ /(\S*\Q$Global::replacestring\E\S*)/ or die ("This should never happen"); my $wordarg = $1; my @all_word_arg; for my $arg (@quoted_args) { my $substituted = $wordarg; - $substituted=~s/$Global::replacestring/$arg/go; + $substituted=~s/\Q$Global::replacestring\E/$arg/go; push @all_word_arg, $substituted; } my $all_word_arg = join(" ",@all_word_arg); @@ -743,7 +745,7 @@ sub generate_command_line { } else { # Normal replace {} with args my $arg=join(" ",@quoted_args); - $job_line =~ s/$Global::replacestring/$arg/go; + $job_line =~ s/\Q$Global::replacestring\E/$arg/go; } } else { # append args @@ -948,11 +950,11 @@ sub user_requested_processes { my $opt_P = shift; if(defined $opt_P) { if($opt_P =~ /^\+(\d+)$/) { - # E.g. -j +2 + # E.g. -P +2 my $j = $1; $processes = $j + no_of_cpus(); } elsif ($opt_P =~ /^-(\d+)$/) { - # E.g. -j -2 + # E.g. -P -2 my $j = $1; $processes = no_of_cpus() - $j; } elsif ($opt_P =~ /^(\d+)\%$/) { @@ -961,7 +963,7 @@ sub user_requested_processes { } elsif ($opt_P =~ /^(\d+)$/) { $processes = $1; if($processes == 0) { - # -j 0 = infinity (or at least close) + # -P 0 = infinity (or at least close) $processes = 2**31; } } else { @@ -1303,6 +1305,19 @@ sub usage { print "parallel [-0cdfgkqsuvxX] [-j num] [command [arguments]] < list_of_arguments\n"; } +sub version { + print join("\n", + "parallel $Global::version", + "Copyright (C) 2007-2010 Free Software Foundation, Inc.", + "License GPLv3+: GNU GPL version 3 or later ", + "This is free software: you are free to change and redistribute it.", + "There is NO WARRANTY, to the extent permitted by law.", + "", + "Written by Ole Tange .\n" + ); +} + + # # Debugging # @@ -1371,5 +1386,4 @@ $main::opt_X = $main::opt_x = $main::opt_k = $main::opt_d = $main::opt_P = $main::opt_i = $main::opt_p = $main::opt_a = $main::opt_version = $main::opt_L = $main::opt_l = $main::opt_show_limits = $main::opt_n = $main::opt_e = $main::opt_t = -$main::opt_E = $main::opt_r = $main::opt_help = $Global::xargs = -$Global::keeporder = 0; +$main::opt_E = $main::opt_r = $Global::xargs = $Global::keeporder = 0; diff --git a/parallel.1 b/parallel.1 index 77de37cd..2080b280 100644 --- a/parallel.1 +++ b/parallel.1 @@ -124,7 +124,7 @@ .\" ======================================================================== .\" .IX Title "PARALLEL 1" -.TH PARALLEL 1 "2010-02-03" "perl v5.10.1" "User Contributed Perl Documentation" +.TH PARALLEL 1 "2010-02-04" "perl v5.10.1" "User Contributed Perl Documentation" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l @@ -221,7 +221,8 @@ Ungroup output. Output is printed as soon as possible. This may cause output from different commands to be mixed. Can be reversed with \fB\-g\fR. .IP "\fB\-v\fR" 9 .IX Item "-v" -Verbose. Print the job to be run on \s-1STDOUT\s0. Can be reversed with \fB\-s\fR. +Verbose. Print the job to be run on \s-1STDOUT\s0. Can be reversed with +\&\fB\-\-silent\fR. .IP "\fB\-\-xargs\fR =item \fB\-m\fR" 9 .IX Item "--xargs =item -m" Multiple. Insert as many arguments as the command line length permits. If @@ -571,10 +572,10 @@ parallel \-\-wait /tmp/comfile will wait until no more locks on the file .IX Header "AUTHOR" Copyright (C) 2007\-10\-18 Ole Tange, http://ole.tange.dk .PP -Copyright (C) 2008\-2009 Ole Tange, http://ole.tange.dk +Copyright (C) 2008\-2010 Ole Tange, http://ole.tange.dk .SH "LICENSE" .IX Header "LICENSE" -Copyright (C) 2007\-2009 Free Software Foundation, Inc. +Copyright (C) 2007\-2010 Free Software Foundation, Inc. .PP This program is free software; you can redistribute it and/or modify it under the terms of the \s-1GNU\s0 General Public License as published by @@ -590,7 +591,7 @@ You should have received a copy of the \s-1GNU\s0 General Public License along with this program. If not, see . .SH "DEPENDENCIES" .IX Header "DEPENDENCIES" -\&\fBparallel\fR uses Perl, and the Perl modules Getopt::Std, IPC::Open3, +\&\fBparallel\fR uses Perl, and the Perl modules Getopt::Long, IPC::Open3, Symbol, IO::File, \s-1POSIX\s0, and File::Temp. .SH "SEE ALSO" .IX Header "SEE ALSO" diff --git a/unittest/actual-results/test15 b/unittest/actual-results/test15 new file mode 100644 index 00000000..7da0051f --- /dev/null +++ b/unittest/actual-results/test15 @@ -0,0 +1,100 @@ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +replace +replace +replace +replace +replace +replace +replace +include this +include this +include this +include this +include this +include this +line 1 +line 2 +line 3 +line 1 line 1 +line 2 +line 1 +line 2 +line 3 +line 1 line 1 +line 2 +line 1 +line 2 +line 3 +line 1 +line 2 +line 3 +line 1 line 1 +line 2 +line 1 line 1 +line 2 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +This is line 1 +This is line 2 +This is line 3 + +This is line 1 +This is line 2 +This is line 3 + +line 1 +line 2 +line 3 +line 1 +line 2 +line 3 +line 1 line 1 +line 2 +line 1 line 1 +line 2 +2 +2 +7 +echo bar +echo car +echo far +echo bar +echo car +echo far diff --git a/unittest/tests-to-run/test15.sh b/unittest/tests-to-run/test15.sh new file mode 100644 index 00000000..d10f7067 --- /dev/null +++ b/unittest/tests-to-run/test15.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +# Test xargs compatibility + + +# Test -a and --arg-file: Read input from file instead of stdin +seq 1 10 >/tmp/$$ +parallel -a /tmp/$$ echo +parallel --arg-file /tmp/$$ echo + +# Test -i and --replace: Replace with argument +(echo a; echo END; echo b) | parallel -k -i -eEND echo repl{}ce +(echo a; echo END; echo b) | parallel -k --replace -eEND echo repl{}ce +(echo a; echo END; echo b) | parallel -k -i+ -eEND echo repl+ce +(echo a; echo END; echo b) | parallel -k --replace + -eEND echo repl+ce +(echo a; echo END; echo b) | parallel -k --replace== -eEND echo repl=ce +(echo a; echo END; echo b) | parallel -k --replace=^ -eEND echo repl^ce +(echo a; echo END; echo b) | parallel -k -I^ -eEND echo repl^ce + +# Test -E: Artificial end-of-file +(echo include this; echo END; echo not this) | parallel -k -E END echo +(echo include this; echo END; echo not this) | parallel -k -EEND echo + +# Test -e and --eof: Artificial end-of-file +(echo include this; echo END; echo not this) | parallel -k -e END echo +(echo include this; echo END; echo not this) | parallel -k -eEND echo +(echo include this; echo END; echo not this) | parallel -k --eof=END echo +(echo include this; echo END; echo not this) | parallel -k --eof END echo + +# Test -n and --max-args: Max number of args per line (only with -X and -m) +(echo line 1;echo line 2;echo line 3) | parallel -k -n1 -m echo +(echo line 1;echo line 1;echo line 2) | parallel -k -n2 -m echo +(echo line 1;echo line 2;echo line 3) | parallel -k -n1 -X echo +(echo line 1;echo line 1;echo line 2) | parallel -k -n2 -X echo +(echo line 1;echo line 2;echo line 3) | parallel -k --max-args=1 -X echo +(echo line 1;echo line 2;echo line 3) | parallel -k --max-args 1 -X echo +(echo line 1;echo line 1;echo line 2) | parallel -k --max-args=2 -X echo +(echo line 1;echo line 1;echo line 2) | parallel -k --max-args 2 -X echo + +# Test --max-procs and -P: Number of processes +seq 1 10 | parallel -k --max-procs +0 echo +seq 1 10 | parallel -k -P 200% echo + +# Test --delimiter and -d: Delimiter instead of newline +# Yes there is supposed to be an extra newline for -d N +echo line 1Nline 2Nline 3 | parallel -k -d N echo This is +echo line 1Nline 2Nline 3 | parallel -k --delimiter N echo This is +printf "line 1\0line 2\0line 3" | parallel -d '\0' echo +printf "line 1\tline 2\tline 3" | parallel --delimiter '\t' echo + +# Test --max-chars and -s: Max number of chars in a line +(echo line 1;echo line 1;echo line 2) | parallel -k --max-chars 25 -X echo +(echo line 1;echo line 1;echo line 2) | parallel -k -s 25 -X echo + +# Test --no-run-if-empty and -r: This should give no output +echo " " | parallel -r echo +echo " " | parallel --no-run-if-empty echo + +# Test --help and -h: Help output (just check we get the same amount of lines) +parallel -h | wc -l +parallel --help | wc -l + +# Test --version: Version output (just check we get the same amount of lines) +parallel --version | wc -l + +# Test --verbose and -t +(echo b; echo c; echo f) | parallel -k -t echo {}ar 2>&1 >/dev/null +(echo b; echo c; echo f) | parallel -k --verbose echo {}ar 2>&1 >/dev/null diff --git a/unittest/wanted-results/test15 b/unittest/wanted-results/test15 new file mode 100644 index 00000000..7da0051f --- /dev/null +++ b/unittest/wanted-results/test15 @@ -0,0 +1,100 @@ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +replace +replace +replace +replace +replace +replace +replace +include this +include this +include this +include this +include this +include this +line 1 +line 2 +line 3 +line 1 line 1 +line 2 +line 1 +line 2 +line 3 +line 1 line 1 +line 2 +line 1 +line 2 +line 3 +line 1 +line 2 +line 3 +line 1 line 1 +line 2 +line 1 line 1 +line 2 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +This is line 1 +This is line 2 +This is line 3 + +This is line 1 +This is line 2 +This is line 3 + +line 1 +line 2 +line 3 +line 1 +line 2 +line 3 +line 1 line 1 +line 2 +line 1 line 1 +line 2 +2 +2 +7 +echo bar +echo car +echo far +echo bar +echo car +echo far