# New Ticket Created by Kay-Uwe Huell # Please include the string: [perl #41353] # in the subject line of all future correspondence about this issue. # <URL: http://rt.perl.org/rt3/Ticket/Display.html?id=41353 >
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Hi parrot team, I have started to call external programs in Linux environment and had to recognize, that open "/some/command some argument", "-|" is VERY limited. (Could not use quotes to use space in arguments, could only use max. 10 arguments, etc.) I have extended the function to be still basic (not to use perl's magic shell usage when it encounters a special character), but that you can use quotes to have whitespace in arguments and that you can have as many arguments you like. Patch attached. Regards, Kiwi src/io/io_unix.c | 147 ++++++++++++++++++++++++++++++++++++++++++++++--------- t/pmc/io.t | 97 +++++++++++++++++++++++++++++++++++- 2 files changed, 220 insertions(+), 24 deletions(-) -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.3 (GNU/Linux) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org iD8DBQFFukPNHSiAp6bcIcgRAiHnAKCoSTz9JChlnwzGIcHckHWwD3b+MQCePyF7 GuV6KPdUgcIJ9NVwkRsQeWg= =Kjo7 -----END PGP SIGNATURE-----
Index: src/io/io_unix.c =================================================================== --- src/io/io_unix.c (Revision 16797) +++ src/io/io_unix.c (Arbeitskopie) @@ -21,8 +21,7 @@ =head2 Functions -=over 4 - +=over 4 =cut */ @@ -1009,16 +1008,25 @@ =item C<static ParrotIO * PIO_unix_pipe(Interp *interp, ParrotIOLayer *l, STRING *cmd, int flags)> -Very limited C<exec> for now. +This function executes C<cmd> and reads from or writes to pipe. +If you read from pipe you usually get only stdout from C<cmd>. If you append +a "2>&1" to your command stderr will be redirected to stdout, which means you +get stderr as well. You may use single quote C<'> or double quote C<"> to use +whitespace in your command's arguments. If you do not match a quote you started +an exception will be thrown. + +Right now there is no shell magic as in perl (recognize special chars and +execute sh -c 'cmd'). You may use C</bin/sh -c 'cmd'> yourself. You always have +to use complete path of command you want to execute, because there is no path +searching! + XXX: Where does this fit, should it belong in the ParrotIOLayerAPI? =cut */ - - static ParrotIO * PIO_unix_pipe(Interp *interp, ParrotIOLayer *l, const char *cmd, int flags) { @@ -1059,9 +1067,44 @@ /* Child - exec process */ if (pid == 0) { - char *argv[10], *p, *c; - int n; + char **argv, *p, *_cmd; + int n, i, doshell = 0, redirect_stderr = 0, argstep = 10 ; + + /* usually STDOUT will be redirected to pipe. + + If you use magic '2>&1' at the end of the command, STDERR will be + redirected as well. + */ + + _cmd = strdup(cmd); + + /* skip whitespace */ + for ( ; *_cmd && isspace(*_cmd) ; _cmd++); + + /* Check for magic '2>&1' at the end of command */ + for (p = _cmd ; ; ) + { + p = strstr(p, "2>&1"); + if (p && isspace(p[-1]) && (!p[4] || isspace(p[4]))) { + char *q = p; + for (p += 4; *p && isspace(*p); p++); + + /* if there is something after 2>&1, 2>&1 belongs to the + command, and we have to check again */ + if (*p) + continue; + + /* 2>&1 and the rest of string has to be cut off */ + *q = '\0'; + redirect_stderr = 1; + break; + } + /* there is no "2>&1" in p */ + break; + } + + if (flags & PIO_F_WRITE) { /* the other end is writing - we read from the pipe */ close(STDIN_FILENO); @@ -1074,27 +1117,85 @@ /* XXX redirect stdout, stderr to pipe */ close(STDIN_FILENO); close(STDOUT_FILENO); - close(STDERR_FILENO); - if ( dup(fds[0]) != STDIN_FILENO || dup(fds[1]) != STDOUT_FILENO - || dup(fds[1]) != STDERR_FILENO ) - { + if ( dup(fds[0]) != STDIN_FILENO || dup(fds[1]) != STDOUT_FILENO ) exit(0); + + if (redirect_stderr) { + close(STDERR_FILENO); + if (dup(fds[1]) != STDERR_FILENO) + exit(0); } } - /* - * XXX ugly hack to be able to pass some arguments - * split cmd at blanks - */ - c = strdup(cmd); - for (n = 0, p = strtok(c, " "); n < 9 && p; p = strtok(NULL, " ")) { - if (n == 0) - cmd = p; - argv[n++] = p; + + argv = mem_sys_allocate(argstep * sizeof(char *)); + + /* now prepare argv array. Split on ' '. Check for "..." and '...' in + string. Watch out for excaped " and ' */ + +/* This local macro checks if count of '\' is odd or equal. + odd => escaped => continue */ +#define CONTINUE_IF_ESCAPED if (p[-1] == '\\') { \ + char *r = p-1; \ + for(; *r && *r == '\\'; r--); \ + if ((p-r-1) % 2 == 1) continue; } + + argv[0] = _cmd; + for (n = 1, p = _cmd; *p; p++) { + if (isspace(*p)) { + /* skip more space */ + for (; isspace(*p); p++ ) + *p = '\0'; + argv[n++] = p; + + /* resize array by 10 */ + if ((n % 10) == 0) { + /* do not use mem_sys_realloc() here. It currupts the argv + array! */ + char **new_argv ; + new_argv = mem_sys_allocate((n + 10) * sizeof(char *)); + memcpy(new_argv, argv, n * sizeof(char *)); + argv = new_argv; + } + } + + if (*p == '"' || *p == '\'') { + char quote_char = *p ; + + CONTINUE_IF_ESCAPED ; + + for (p++ ; ; ) { + p = strchr(p, quote_char) ; + if (!p) { + /* exception no matching quote_char */ + real_exception(interp, NULL, E_ValueError, + "no matching '%c'", quote_char); + } + CONTINUE_IF_ESCAPED ; + break; + } + } } - argv[n] = NULL; - execv(cmd, argv); /* XXX use execvp ? */ + + argv[n] = NULL ; + + for (i = 0 ; argv[i] != NULL ; i++) { + /* remove quoting chars */ + if (argv[i][0] == '"' || argv[i][0] == '\'') + { + char *p = strrchr(argv[i], argv[i][0]); + (argv[i])++ ; *p = '\0' ; + } + } + execv(argv[0], argv); + /* Will never reach this unless exec fails. */ - perror("execvp"); + perror("execv"); + fprintf(stderr, "cmd: %s\n", argv[0]); + for (i = 0 ; i < n ; i++) + { + fprintf(stderr, "%d: %s\n", i, argv[i]); + } + exit(1); } Index: t/pmc/io.t =================================================================== --- t/pmc/io.t (Revision 16797) +++ t/pmc/io.t (Arbeitskopie) @@ -6,7 +6,7 @@ use warnings; use lib qw( . lib ../lib ../../lib ); use Test::More; -use Parrot::Test tests => 44; +use Parrot::Test tests => 47; =head1 NAME @@ -902,6 +902,101 @@ OUTPUT } +SKIP: { + skip 'will not work on windows', 1 if $^O eq 'MSWin32'; + open IN, "/bin/sh -c \"FOO=. ls\" |" ; + my $text ; + $text .= $_ while <IN> ; + close IN ; + # + # I need to have separeted stdout and stderr for testing unix_pipe + # + open CODE_FILE, ">/tmp/$$.pir" ; + print CODE_FILE <<'CODE' ; +.sub execute + .param string cmd + + .local pmc pipe + pipe = open cmd, "-|" + unless_null pipe, READ_PIPE + print "error\n" + exit 1 + + READ_PIPE: + # unbuffered read + $S0 = pop pipe + + READ_NEXT: + $S0 = read pipe, 10 + $S1 .= $S0 + if pipe goto READ_NEXT + + DONE: + close pipe + + .return($S1) +.end + +.sub main :main + $S0 = execute("/bin/sh -c \"echo foo 1>&2 ; echo bar\"") + print $S0 + $S0 = execute("/bin/sh -c 'echo foo 1>&2 ; echo \"bar\" 2>&1'") + print $S0 + $S0 = execute("/bin/sh -c \"echo foo 1>&2 ; echo bar\" 2>&1") + print $S0 + exit 0 +.end +CODE + close CODE_FILE ; + + my $stdout = `./parrot /tmp/$$.pir 2>/dev/null` ; + my $stderr = `./parrot /tmp/$$.pir 1>/dev/null 2>/tmp/$$.err ; cat /tmp/$$.err` ; + unlink "/tmp/$$.pir" ; + unlink "/tmp/$$.err" ; + + ok($stdout eq "bar\nbar\nfoo\nbar\n", "pipe's output to stdout"); + ok($stderr eq "foo\nfoo\n", "pipe's output to stderr"); +} + +SKIP: { + skip 'will not work on windows', 1 if $^O eq 'MSWin32'; + open IN, "echo 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 |" ; + my $text ; + $text .= $_ while <IN> ; + close IN ; + pir_output_is(<<'CODE', <<"OUTPUT", "many arguments"); +.sub main :main + .local pmc pipe + pipe = open "/bin/echo 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6", "-|" + unless_null pipe, READ_PIPE + print "error\n" + exit 1 + + READ_PIPE: + $S0 = pop pipe + print "popped " + print $S0 + print "\n" + + READ_NEXT: + $S0 = read pipe, 10 + $S1 .= $S0 + if pipe goto READ_NEXT + + DONE: + close pipe + + print $S1 + print "\n" + exit 0 +.end +CODE +popped buf +$text +OUTPUT +} + + # Local Variables: # mode: cperl # cperl-indent-level: 4
unix_pipe.patch.sig
Description: Binary data