From ca6273b44a7318a608c11fd03c4484d83d727812 Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Thu, 27 Apr 2023 02:11:08 +1000 Subject: [PATCH] fix 2>@1 etc redirections, sh_runXXX procs for sh -c to allow * expansion by shell --- src/modules/punk-0.1.tm | 6 +- src/modules/shellfilter-0.1.8.tm | 31 +++++- src/modules/shellrun-0.1.tm | 135 +++++++++++++++++---------- src/punk86.vfs/lib/app-punk/repl.tcl | 5 +- 4 files changed, 116 insertions(+), 61 deletions(-) diff --git a/src/modules/punk-0.1.tm b/src/modules/punk-0.1.tm index 81d38cfb..b5ceff81 100644 --- a/src/modules/punk-0.1.tm +++ b/src/modules/punk-0.1.tm @@ -495,11 +495,7 @@ namespace eval punk { interp alias {} ECHO {} punk::sh_ECHO interp alias {} c {} clear - interp alias {} a+ {} shellfilter::ansi::+ - interp alias {} run {} shellrun::run - interp alias {} runout {} shellrun::runout - interp alias {} runerr {} shellrun::runerr - interp alias {} runx {} shellrun::runx + interp alias {} help {} punk help interp alias {} aliases {} punk aliases diff --git a/src/modules/shellfilter-0.1.8.tm b/src/modules/shellfilter-0.1.8.tm index ec97f3ff..b4d6a3fa 100644 --- a/src/modules/shellfilter-0.1.8.tm +++ b/src/modules/shellfilter-0.1.8.tm @@ -1996,6 +1996,7 @@ namespace eval shellfilter { chan configure $wrerr -blocking 0 set lastitem [lindex $commandlist end] + #todo - ensure we can handle 2> file (space after >) if {[string trim [lindex $commandlist end]] eq "&"} { set name [lindex $commandlist 0] @@ -2006,10 +2007,24 @@ namespace eval shellfilter { return [list pids $pidlist] } + #review - reconsider the handling of redirections such that tcl-style are handled totally separately to other shell syntaxes! + # + #note 2>@1 must ocur as last word for tcl - but 2@stdout can occur elsewhere + #(2>@stdout echoes to main stdout - not into pipeline) + #To properly do pipelines it looks like we will have to split on | and call this proc multiple times and wire it up accordingly (presumably in separate threads) set custom_stderr "" if {[string trim $lastitem] in [list {2>&1} {2>@1}]} { set custom_stderr {2>@1} ;#use the tcl style set commandlist [lrange $commandlist 0 end-1] + } else { + # 2> filename + # 2>> filename + # 2>@ openfileid + set redir2test [string range $lastitem 0 1] + if {$redir2test eq "2>"} { + set custom_stderr $lastitem + set commandlist [lrange $commandlist 0 end-1] + } } set lastitem [lindex $commandlist end] @@ -2177,8 +2192,11 @@ namespace eval shellfilter { } #todo - handle custom redirection of stderr to a file? if {[string length $custom_stderr]} { - ::shellfilter::log::write "LAUNCH open |[concat $commandlist $custom_stderr] a+" - set rdout [open |[concat $commandlist $custom_stderr] a+] + #::shellfilter::log::write $runtag "LAUNCH open |[concat $commandlist $custom_stderr] a+" + #set rdout [open |[concat $commandlist $custom_stderr] a+] + ::shellfilter::log::write $runtag "LAUNCH open |[concat $commandlist [list $custom_stderr <@$inchan]] [list RDONLY]" + set rdout [open |[concat $commandlist [list <@$inchan $custom_stderr]] [list RDONLY]] + set rderr "bogus" ;#so we don't wait for it } else { ::shellfilter::log::write $runtag "LAUNCH open |[concat $commandlist [list 2>@$wrerr <@$inchan]] [list RDONLY]" #set rdout [open |[concat $commandlist [list 2>@$wrerr]] a+] @@ -2192,7 +2210,13 @@ namespace eval shellfilter { set rdout [open |[concat $commandlist [list 2>@$wrerr <@$inchan]] [list RDONLY]] + + chan configure $rderr -buffering $errbuffering -blocking 0 + chan configure $rderr -translation $readprocesstranslation } + + + set command_pids [pid $rdout] #puts stderr "command_pids: $command_pids" #tcl::process ensemble only available in 8.7+ - and it didn't prove useful here anyway @@ -2214,9 +2238,8 @@ namespace eval shellfilter { #jjj - chan configure $rderr -buffering $errbuffering -blocking 0 + chan configure $rdout -buffering $outbuffering -blocking 0 - chan configure $rderr -translation $readprocesstranslation chan configure $rdout -translation $readprocesstranslation if {![string length $custom_stderr]} { diff --git a/src/modules/shellrun-0.1.tm b/src/modules/shellrun-0.1.tm index b505cbef..34cfff93 100644 --- a/src/modules/shellrun-0.1.tm +++ b/src/modules/shellrun-0.1.tm @@ -19,23 +19,40 @@ namespace eval shellrun { variable runerr - proc run {args} { - set ::punk::last_run_display [list] - #we provide -nonewline for 'run' even though run doesn't deliver stderr or stdout to the tcl return value + proc get_run_opts {arglist} { + if {[catch { + set callerinfo [info level -1] + } errM]} { + set caller "" + } else { + set caller [lindex $callerinfo 0] + } + + #we provide -nonewline even for 'run' even though run doesn't deliver stderr or stdout to the tcl return value #This is for compatibility with other runX commands, and the difference is also visible when calling from repl. set known_runopts [list "-echo" "-e" "-nonewline" "-n"] set aliases [list "-e" "-echo" "-echo" "-echo" "-n" "-nonewline" "-nonewline" "-nonewline"] ;#include map to self set runopts [list] set cmdargs [list] - set idx_first_cmdarg [lsearch -not $args "-*"] - set runopts [lrange $args 0 $idx_first_cmdarg-1] - set cmdargs [lrange $args $idx_first_cmdarg end] + set idx_first_cmdarg [lsearch -not $arglist "-*"] + set runopts [lrange $arglist 0 $idx_first_cmdarg-1] + set cmdargs [lrange $arglist $idx_first_cmdarg end] foreach o $runopts { if {$o ni $known_runopts} { - error "run: Unknown runoption $o" + error "$caller: Unknown runoption $o - known options $known_runopts" } } set runopts [lmap o $runopts {dict get $aliases $o}] + return [list runopts $runopts cmdargs $cmdargs] + } + + proc run {args} { + set ::punk::last_run_display [list] + + set splitargs [get_run_opts $args] + set runopts [dict get $splitargs runopts] + set cmdargs [dict get $splitargs cmdargs] + if {"-nonewline" in $runopts} { set nonewline 1 } else { @@ -52,7 +69,7 @@ namespace eval shellrun { set c [shellfilter::ansi::+ green] set n [shellfilter::ansi::+] if {[dict exists $exitinfo error]} { - error [dict get $exitinfo error] + error "[dict get $exitinfo error]\n$exitinfo" } return $exitinfo @@ -65,19 +82,10 @@ namespace eval shellrun { set runout "" set runerr "" - set known_runopts [list "-echo" "-e" "-nonewline" "-n"] - set aliases [list "-e" "-echo" "-echo" "-echo" "-n" "-nonewline" "-nonewline" "-nonewline"] ;#include map to self - set runopts [list] - set cmdargs [list] - set idx_first_cmdarg [lsearch -not $args "-*"] - set runopts [lrange $args 0 $idx_first_cmdarg-1] - set cmdargs [lrange $args $idx_first_cmdarg end] - foreach o $runopts { - if {$o ni $known_runopts} { - error "runout: Unknown runoption $o" - } - } - set runopts [lmap o $runopts {dict get $aliases $o}] + set splitargs [get_run_opts $args] + set runopts [dict get $splitargs runopts] + set cmdargs [dict get $splitargs cmdargs] + if {"-nonewline" in $runopts} { set nonewline 1 } else { @@ -177,19 +185,11 @@ namespace eval shellrun { variable runerr set runout "" set runerr "" - set known_runopts [list "-echo" "-e" "-nonewline" "-n"] - set aliases [list "-e" "-echo" "-echo" "-echo" "-n" "-nonewline" "-nonewline" "-nonewline"] ;#include map to self - set runopts [list] - set cmdargs [list] - set idx_first_cmdarg [lsearch -not $args "-*"] - set runopts [lrange $args 0 $idx_first_cmdarg-1] - set cmdargs [lrange $args $idx_first_cmdarg end] - foreach o $runopts { - if {$o ni $known_runopts} { - error "runerr: Unknown runoption $o" - } - } - set runopts [lmap o $runopts {dict get $aliases $o}] + + set splitargs [get_run_opts $args] + set runopts [dict get $splitargs runopts] + set cmdargs [dict get $splitargs cmdargs] + if {"-nonewline" in $runopts} { set nonewline 1 } else { @@ -272,6 +272,7 @@ namespace eval shellrun { return $::shellrun::runerr } + proc runx {args} { set ::punk::last_run_display [list] variable last_run_display @@ -280,32 +281,22 @@ namespace eval shellrun { set runout "" set runerr "" - set known_runopts [list "-echo" "-e" "-nonewline" "-n"] - set aliases [list "-e" "-echo" "-echo" "-echo" "-n" "-nonewline" "-nonewline" "-nonewline"] ;#include map to self - set runopts [list] - set cmdargs [list] - set idx_first_cmdarg [lsearch -not $args "-*"] - set runopts [lrange $args 0 $idx_first_cmdarg-1] - set cmdargs [lrange $args $idx_first_cmdarg end] - foreach o $runopts { - if {$o ni $known_runopts} { - error "runx: Unknown runoption $o - known options $known_runopts" - } - } - set runopts [lmap o $runopts {dict get $aliases $o}] + set splitargs [get_run_opts $args] + set runopts [dict get $splitargs runopts] + set cmdargs [dict get $splitargs cmdargs] + if {"-nonewline" in $runopts} { set nonewline 1 } else { set nonewline 0 } - - #shellfilter::stack::remove stdout $::repl::id_outstack if {"-echo" in $runopts} { - set stderr_stackid [shellfilter::stack::add stderr tee_to_var -action sink-locked -settings {-varname ::shellrun::runerr}] - set stdout_stackid [shellfilter::stack::add stdout tee_to_var -action sink-locked -settings {-varname ::shellrun::runout}] + #float to ensure repl transform doesn't interfere with the output data + set stderr_stackid [shellfilter::stack::add stderr tee_to_var -action float -settings {-varname ::shellrun::runerr}] + set stdout_stackid [shellfilter::stack::add stdout tee_to_var -action float -settings {-varname ::shellrun::runout}] } else { #set stderr_stackid [shellfilter::stack::add stderr var -action sink-locked -settings {-varname ::shellrun::runerr}] #set stdout_stackid [shellfilter::stack::add stdout var -action sink-locked -settings {-varname ::shellrun::runout}] @@ -385,4 +376,46 @@ namespace eval shellrun { #always return exitinfo $code at beginning of dict (so that punk unknown can interpret the exit code as a unix-style bool if double evaluated) return [list {*}$exitinfo stdout $::shellrun::runout stderr $::shellrun::runerr] } + + proc sh_run {args} { + set splitargs [get_run_opts $args] + set runopts [dict get $splitargs runopts] + set cmdargs [dict get $splitargs cmdargs] + #e.g sh -c "ls -l *" + #we pass cmdargs to sh -c as a list, not individually + tailcall shellrun::run {*}$runopts sh -c $cmdargs + } + proc sh_runout {args} { + set splitargs [get_run_opts $args] + set runopts [dict get $splitargs runopts] + set cmdargs [dict get $splitargs cmdargs] + tailcall shellrun::runout {*}$runopts sh -c $cmdargs + } + proc sh_runerr {args} { + set splitargs [get_run_opts $args] + set runopts [dict get $splitargs runopts] + set cmdargs [dict get $splitargs cmdargs] + tailcall shellrun::runerr {*}$runopts sh -c $cmdargs + } + proc sh_runx {args} { + set splitargs [get_run_opts $args] + set runopts [dict get $splitargs runopts] + set cmdargs [dict get $splitargs cmdargs] + tailcall shellrun::runx {*}$runopts sh -c $cmdargs + } } + +namespace eval shellrun { + interp alias {} a+ {} shellfilter::ansi::+ + + interp alias {} run {} shellrun::run + interp alias {} sh_run {} shellrun::sh_run + interp alias {} runout {} shellrun::runout + interp alias {} sh_runout {} shellrun::sh_runout + interp alias {} runerr {} shellrun::runerr + interp alias {} sh_runerr {} shellrun::sh_runerr + interp alias {} runx {} shellrun::runx + interp alias {} sh_runx {} shellrun::sh_runx + +} + diff --git a/src/punk86.vfs/lib/app-punk/repl.tcl b/src/punk86.vfs/lib/app-punk/repl.tcl index 81c03b16..bc019397 100644 --- a/src/punk86.vfs/lib/app-punk/repl.tcl +++ b/src/punk86.vfs/lib/app-punk/repl.tcl @@ -724,8 +724,11 @@ proc repl::repl_handler {chan} { [string equal -length [string length "../ "] "../ " $command] || \ [string equal "../" $command] || \ [string equal -length [string length "runx "] "runx " $command] || \ + [string equal -length [string length "sh_runx "] "sh_runx " $command] || \ [string equal -length [string length "runout "] "runout " $command] || \ - [string equal -length [string length "runerr "] "runerr " $command] + [string equal -length [string length "sh_runout "] "sh_runout " $command] || \ + [string equal -length [string length "runerr "] "runerr " $command] || \ + [string equal -length [string length "sh_runerr "] "sh_runerr " $command] } { if {[llength $last_run_display]} {