4

I am experimenting with TCL command exec in tclsh and here are my results:

% set show_me_dir "ls"
ls
% exec $show_me_dir
VboxSharedFolder
% set show_me_dir "ls -la"
ls -la
% exec $show_me_dir
couldn't execute "ls -la": no such file or directory
% set show_me_dir {ls -la}
ls -la
% exec $show_me_dir
couldn't execute "ls -la": no such file or directory
% ls -la
total 141
d---------+ 1 wakatana Domain Users     0 Jan 22 19:12 .
d---------+ 1 wakatana Domain Users     0 Apr 16  2014 ..
----------+ 1 wakatana Domain Users 20214 Jan 23 18:43 .bash_history
----------+ 1 wakatana Domain Users  1494 Apr 15  2014 .bash_profile
----------+ 1 wakatana Domain Users  7593 Jan 22 19:03 .bashrc
d---------+ 1 wakatana Domain Users     0 Jan 15 14:56 VboxSharedFolder
%

Can somebody please explain how can I execute command with arguments?

Edit:

The following example from Expanding a list of parameters in Tcl and eval article was big eye opener of what is going on here:

The variable $action is only expanded into the string "piemiddle apple" AFTER the command line has been split into its individual parameters:

% set action {piemiddle apple}
% set $action
can't read "piemiddle apple": no such variable

Result: set command "sees" one argument, equivalent to:

% set {piemiddle apple}

The expand operator allows you to specify that a variable is to be expanded BEFORE the command line is split into individual parameters:

% set action {piemiddle apple}
% set {*}$action
apple

Result: set command "sees" two arguments, equivalent to:

% set piemiddle apple

In earlier versions of Tcl, the eval command was the recommended alternative and it remains available today.

% set action {piemiddle apple}
% eval set $action
apple

Another examples which proves functionality of expansion operator:

% set {*}"name Linus"
Linus
% puts $name
Linus
%
%
% set distro Unbuntu
Unbuntu
% set {*}"linux $distro"
Unbuntu
% puts $linux
Unbuntu
%
%

Finally the discovery that exec needs command as it's first argument and first command option as it's second argument etc.

% exec "ls" "-la"
total 137
d---------+ 1 wakatana Domain Users     0 Jan 22 19:12 .
d---------+ 1 wakatana Domain Users     0 Apr 16  2014 ..
----------+ 1 wakatana Domain Users 20214 Jan 23 18:43 .bash_history
----------+ 1 wakatana Domain Users  1494 Apr 15  2014 .bash_profile
----------+ 1 wakatana Domain Users  7593 Jan 22 19:03 .bashrc
d---------+ 1 wakatana Domain Users     0 Jan 15 14:56 VboxSharedFolder
%
%
% exec "ls -la"
couldn't execute "ls -la": no such file or directory
Wakan Tanka
  • 7,542
  • 16
  • 69
  • 122
  • 2
    Please take a moment to reflect on the subject of argument expansion, which was explained to you in the answers to your previous question. – Peter Lewerin Jan 22 '15 at 18:29

2 Answers2

6

The safest way to build a command for exec is to use Tcl's list. For example:

% set tcl_version
8.5
% set cmd [list ls -l tmp]
ls -l tmp
% eval exec $cmd
total 32
-rw-r--r--  1 pynexj  staff  1176 Jan 23 23:24 file.txt
-rw-r--r--  1 pynexj  staff  1176 Jan 23 23:24 foo-1.dat
-rw-r--r--  1 pynexj  staff  1176 Jan 23 23:24 foo-2.dat
-rw-r--r--  1 pynexj  staff  1176 Jan 23 23:24 foo-3.dat
% exec {*}$cmd
total 32
-rw-r--r--  1 pynexj  staff  1176 Jan 23 23:24 file.txt
-rw-r--r--  1 pynexj  staff  1176 Jan 23 23:24 foo-1.dat
-rw-r--r--  1 pynexj  staff  1176 Jan 23 23:24 foo-2.dat
-rw-r--r--  1 pynexj  staff  1176 Jan 23 23:24 foo-3.dat
%

Note that {*} is a new syntax of Tcl 8.5 which can help reduce the uses of eval.

pynexj
  • 19,215
  • 5
  • 38
  • 56
  • Isn't TCL list just ordinary strings where each word is separated with space? I think `set cmd [list ls -la /tmp]` and `set cmd "ls -la /tmp"` are the same. – Wakan Tanka Jan 24 '15 at 12:43
  • Note that 8.4 is out of support now. We will no longer do even security patches for it. Not that we're aware of anything in it that _needs_ patches that wouldn't be better fixed by just going to 8.5 anyway. The level of compatibility between the two is mostly very high (unless you're relying on integers being a fixed width, in which case your code already isn't portable). – Donal Fellows Jan 24 '15 at 15:14
  • @WakanTanka: The are not the same. For example, just think how you can do the same thing as `set cmd [list ls -l "foo bar.txt"]` without using `list`. And `set cmd [list echo \[]`? – pynexj Jan 26 '15 at 01:44
  • @whjm: e.g. by using escaping with backslash? It's a little bit ugly syntax but it will work I suppose. – Wakan Tanka Jan 26 '15 at 08:44
  • Yes by using backslash escapes we can construct valid lists. But it's not always easy and it's error prone. – pynexj Jan 26 '15 at 15:27
  • @whjm: I agree, but as I said those two mentioned constructions does exactly the same thing in the end. – Wakan Tanka Jan 26 '15 at 16:01
  • @WakanTanka: For this specific case, yes. But don't think you can always construct correct lists this way especially when you don't know in advance what the data would look like. :) – pynexj Jan 27 '15 at 01:39
1

As example for ls command you can do:

exec {*}ls -lsa {*}[glob *.cpp]

Please have a look at What does {*} do in TCL?

Community
  • 1
  • 1
Klaus
  • 24,205
  • 7
  • 58
  • 113