tcl exec with file in parameter doesn't work - file

I'm working on a file explorer inTCL/Tk and I want to add a little thing to execute commands with the current selection (using %l %f) %l executing with the full list and %f executing the commmand with each file.
my only problem is that if I type a command like "gedit" for eg it works but as soon as I type a command with argument it doesn't work ...
I've been looking everywhere and I don't get it...
If someone could help me...
btw getl var Name is a function that returns me a FileName in full path (/home/...) and if I return the string that is supposed to be executed and put it in a terminal it works...
Here is the code:
proc tl_exec {liste command } {
#lorsqu'il faut effectué la commande avec la liste en param
if { [string first "%l" $command] > 0} {
foreach v $liste {
lappend args [getl $v Name]
}
set com [string map [list "%l" [join $args " "] ] $command ]
puts $com
set val [exec [split $com " "] ]
} elseif { [string first "%f" $command] > 0} {
#lorsqu'il faut effectué la commande pour chaque fichier
foreach v $liste {
set com [string map list ["%f" [getl $v "Name"] ] $command ]
lappend val [ exec [split $com " "] ]
}
} else {
#lorsqu'on a pas de fichiers
set val [exec $command]
}
}
Thanks a lot

Your code has more than a single problem, it will probably break with special chars or spaces in filenames too, as you do not quoting at all.
But you are right about exec considering everything as a single command.
set val [exec [split $com " "] ]
does not do what you expect, split returns a list, but does not automagically turn that list into extra args for exec.
If you use Tcl 8.5 you can try:
set val [exec {*}[split $com " "] ]
which turns the list into single arguments to pass to exec.
But the code you use is brittle in general, as you do not handle any exit codes or programs writing to stderr, so a more elaborate solution would be needed to be robust.
Have a look at http://wiki.tcl.tk/1039 especially the discussions on the bottom of the page.

Related

How can I redirect the powershell output to my tcl-file?

Inside a .tcl file the batch file "test.ps1" is executed.
set output [exec test.ps1]
puts $output
Edit:
When I simply execute directly the test.ps1 file I can see all outputs in a shell-window. If I call the .tcl file I do not see the output only at the end when the batch file finished. All output text is written at the end but not updated when the ps1-file is still running.
What I see is that the application, where the .tcl file is called, goes to a "freeze" state so it is not possible to use that GUI as long the .ps1 file is running. At the end of the ps1 file all output is written at once and I can use the application again.
Question:
Is there a way to continously update the shell window in order to see the output?
You can execute the command in background using & and redirect its output to the tcl script output using >#stdout:
exec test.ps1 >#stdout &
Check the Tcl exec docs for details on how this works.
What about this:
################################################################################
##### Calls a single Powershell command (blocking, hidden)
### Arg: The command to give to Powershell via -command switch
### Ret: A List of three elements:
### -1 "" <errtext> -> error from twapi::create_process
### 0 <stdouttxt> "" -> Ok
### 1 "..." <stderrtext> -> Maybe Ok, something written to stderr
#
proc execPowershellCmd {cmd} {
set cmd "-command $cmd"
foreach chan {stdin stdout stderr} {
lassign [chan pipe] rd$chan wr$chan
}
if {[catch {
set cmd [string map [list \" \\\"] $cmd]; # muss noch in Wiki...
twapi::create_process [auto_execok powershell] -cmdline $cmd -showwindow hidden \
-inherithandles 1 -stdchannels [list $rdstdin $wrstdout $wrstderr]
} ret]} {
return [list -1 "" $ret]
}
chan close $wrstdin; chan close $rdstdin; chan close $wrstdout; chan close $wrstderr
foreach chan [list $rdstdout $rdstderr] {
chan configure $chan -encoding cp850 -blocking true; # -buffering full?; # -enc?
}
set out [read $rdstdout]; set err [read $rdstderr]
chan close $rdstdout; chan close $rdstderr
return [list [string compare $err ""] $out $err]
}

TCL create a list

I'm an absolute beginner and can't quite wrap my head around Tcl. I need some help with something that I think is very basic. Any help would be appreciated. I have a text file that I want to import into Tcl. I'll give you the syntax of the file and my desired way to store it:
text FILE to import into Tcl:
Singles 'pink granny fuji'
Singles2 'A B C D E'
Couples 'bread butter honey lemon cinnamon sugar'
Couples2 'A1 A2 B1 B2 C1 C2 D1 D2'
My desired format:
For lines 1 & 2:
Singles
[pink granny fuji] ( 3 single elements)
Singles2
[A B C D E] (5 single elements)
For lines 3 & 4:
Couples
[bread butter
honey lemon
cinnamon sugar] (3 x 2 array)
Couples2
[A1 A2
B1 B2
C1 C2
D1 D2] (4 x 2 array)
The import text file can in theory have any number of elements, but lines 3&4 will always be an even number of elements so that they are pairs, so I know a for each loop is needed to capture any number of elements. I know the code will also need to strip the apostrophes from the text file.
I'm just really struggling at the moment, really appreciate any help at all, thank you :)
here is solution. It works perfectly. I hope you are looking for similar solution.
set handle [open "text_import" "r"]
set content [read $handle]
regsub -all {'} $content "" content
#set content [split [read $handle] \n]
set content [split $content \n]
foreach ele $content {
puts $ele
if {[regexp -nocase -- "(\[^ \]+) +(.*)" $ele - key val]} {
puts $key
if {[regexp -nocase -- "single" $key]} {
set val1 [split $val " "]
set arr($key) $val1
} elseif {[regexp -nocase -- "couple" $key]} {
set biggerList [list]
set val2 [split $val " "]
for {set i 0} {$i < [llength $val2]} {incr i 2} {
set tempList [list [lindex $val2 $i] [lindex $val2 [expr $i + 1]]]
lappend biggerList $tempList
}
set arr($key) $biggerList
}
}
}
parray arr
~
One possible solution. Instead of loading the textfile as data, we can load it as Tcl source if we make the right definitions.
proc Singles args {
set str [string trim [join $args] ']
puts "\[$str]"
}
proc Singles2 args {
set str [string trim [join $args] ']
puts "\[$str]"
}
proc Couples args {
set list [split [string trim [join $args] ']]
foreach {a b} $list {
lappend list2 "$a $b"
}
set str [join $list2 \n]
puts "\[$str]"
}
proc Couples2 args {
set list [split [string trim [join $args] ']]
foreach {a b} $list {
lappend list2 "$a $b"
}
set str [join $list2 \n]
puts "\[$str]"
}
source textfile.txt
Documentation:
foreach,
join,
lappend,
list,
proc,
puts,
set,
source,
split,
string

Accessing array elements in shell

I have an array as:
Option[0]=$3
Option[1]=$4
Option[2]=$5
Option[3]=$6
Option[4]=$7
Option[5]=$8
I have to access array elements in shell script.
I know the format is "${Option[0]}" but this format is not accepted in my system.
Its giving me error "Bad substitution".
I have found the solution as:
Arrays are not possible in Busybox shell but a tweak is possible like:
Option="$3; $4; $5; $6; $7; $8"
I have used ; because some of the elements were having "space in the string". Like $3 = " Hello World"
And while iterating the array, delimeter was a space itself.
So to modify the delimiter ; was used.
For iterating the above array:
IFS=$';'
for opt in $Option
do
if [ $opt != " " ]; then
echo "$opt"
fi
done

perl hash of arrays issue

I have few lines in my array #lines in which * shows me the start time of a command (like sync/fetch) and the line with same processID pid and the command without * shows me the end time. They may not be continuous always. I would like to get the startdate and enddate of a particular processID and cmd. Like for usera the cmd sync with processID 11859 started at 2015/01/13 13:53:01.491-05:00 and ended at 2015/01/13 13:55:01.492-05:00
Below is my approach in which I took a hash of array and used processID as key and did split the lines. This works fine only when the start and end lines of a command are continuous , but how can I make it work even when they are not continuous.
my %users;
foreach my $line (#lines) {
if ($line =~ m{(\*)+}) {
($stdate, $sttime, $pid, $user, $cmd) = split ' ', $line;
$startdate ="$stdate $sttime";
}
else {
($eddate, $edtime, $pid, $user, $cmd) = split ' ', $line;
$enddate = "$eddate $edtime";
}
$users{$pid} = [ $startdate, $enddate, $user, $cmd ];
}
Content in #lines:
2015/01/13 13:53:01.491-05:00 11859 usera *sync_cmd 7f1f9bfff700 10.101.17.111
2015/01/13 13:57:02.079-05:00 11863 userb *fetch_cmd 7f1f9bfff700 10.101.17.111
2015/01/13 13:59:02.079-05:00 11863 userb fetch_cmd 7f1f9bfff700 10.101.17.111
2015/01/13 13:55:01.492-05:00 11859 usera sync_cmd 7f1f9bfff700 10.101.17.111
I'm looking at your code and wondering why you're using a hash of arrays.
As far as I'm concerned, the purpose of array is a set of similar but ordered values.
Could you not instead do:
my %processes;
foreach (#lines) {
my ( $date, $time, $pid, $user, $cmd, #everything_else ) = split;
if ( $cmd =~ m/^\*/ ) {
#if command starts with a * - it started.
if ( defined $processes{$pid} ) {
print "WARNING: $pid reused\n";
}
$processes{$pid}{'start_date'} = $date;
$processes{$pid}{'time'} = $time;
$processes{$pid}{'user'} = $user;
$processes{$pid}{'cmd'} = $cmd;
}
else {
#cmd does not start with '*'.
if ( $processes{$pid}{'cmd'} =~ m/$cmd/ ) {
#this works, because 'some_command' is a substring of '*some_command'.
$processes{$pid}{'end_date'} = $date;
$processes{$pid}{'end_time'} = $time;
}
else {
print
"WARNING: $pid has a command of $cmd, where it started with $processes{$pid}{'cmd'}\n";
}
}
}
You might want some additional validation tests in case you've got e.g. a long enough log that pids get reused, or e.g. you've got a log that doesn't include both start and finish of a particular process.
When you assign to %users{$pid} you are presuming that the most recent $startdate and $enddate are both relevant. This problem is exacerbated by the fact that your variables that hold your field values have a scope larger than the foreach loop, allowing these values to bleed between records.
In the if block, you should assign the values of $startdate, $user, $cmd to the array. Individually or as a slice if you like. In the else block you should assign $enddate to it's element in the array.
Regex extra credit: You don't seem to really care if there is more that one * in a record, making the + in the regex superfluous. As an added bonus, without it the capturing group is also of no value. m{\*} should do quite nicely.

Perl printing array values getting weird

I am experiencing something that i don't understand here's my code :
#iprouteur = split( /./, $arraylist[1] );
print "lip du routeur est $arraylist[1]\n";
for ( $i = 2; $i <= $#arraylist; $i++ ) {
print "we found secondary which is $arraylist[$i]\n";
#secondary = split( /./, $arraylist[$i] );
print "voici les ip a comparer : $iprouteur[0] $iprouteur[1] $iprouteur[2] et $secondary[0] $secondary[1] $secondary[2] \n";
if ( $iprouteur[0] eq $secondary[0] && $iprouteur[1] eq $secondary[1] && $iprouteur[2] eq $secondary[2] ) {
print "we need to splice \n";
}
}
The output is like :
lip du routeur est 126.x.x.x
we found secondary which is 126.x.x.x/24
voici les ip a comparer : et
we need to splice
Why perl can't find what is inside the $iprouteur[x] and $secondary[y] variable ?
The problem is that you split with the regex /./. The period . is a meta character, and it is the wildcard, matching any char except newline. So it consumes your entire string when used, and returns a bunch of empty strings. The solution is to escape the period:
#secondary = split(/\./, $arraylist[$i]);
# ^--- note the backslash
Also what I meant in the comments is that this line:
print "voici les ip a comparer : $iprouteur[0] $iprouteur[1] $iprouteur[2] et $secondary[0] $secondary[1] $secondary[2] \n";
can be written:
print "voici les ip a comparer : #iprouteur[0,1,2] et #secondary[0,1,2] \n";
Which is easier both to read and to write.
My bad, i forgot to use "." with the split function
I forgot that it was a special char.
#iprouteur = split(/\./,$arraylist[1]);
#secondary = split(/\./, $arraylist[$i]);

Resources