Why tcl script runs as shell script? - tcl

I don't program in TCL but I do use them such as tkcvs and tkdiff. I notice that they declare themselves as shell script
#!/bin/sh
#-*-tcl-*-
What's more, running them through tclsh doesn't work either and I get error like this:
Initialization failed
The second line in the header baffles me too because AFAIK, shell only looks at the #! line. How's this working?

Tcl scripts are normally setup to run using slightly more than you have shown. It is typically and most robustly done like the following:
#!/bin/sh
# \
exec tclsh "$0" ${1+"$#"}
They use the shell initially because there was no standard installation location for tcl so the location could not be relied on. Instead, by starting the system shell and using that to start the tclsh executable you could be certain to run the installed tclsh as long as it was present on the PATH. The shell evaluates the script and sees the exec tclsh "$0" which causes it to execute the installed tclsh binary and pass it $0 (the script file name) as the first argument, which re-runs the script using the tcl interpreter.
The second line in my example comments out the third line when the script is evaluated by the tcl interpreter. The backslash causes the second and third lines to be treated as a single comment when read by tclsh so that tcl doesn't try and run the exec again.
In your snipped the # -*-tcl-*- is marker to indicate the mode to be used by emacs when editing the file. See the documentation.
There is not really enough to go on for the error message. It doesn't seem to be from the Tcl interpreter itself. (That is 'git grep' in the tcl sources does not match that string).

Related

how to implement a exec command in tcl?

It is possible to call other programs from Tcl using the Tcl exec function. Let’s use this
command to create a Tcl script that will take all of the Tcl code you have written so far and
create a single PDF from it. The filename of the single PDF file should be TCL CODE.pdf.
Recall that the Tcl foreach command makes it very easy to go through a list of items.
You should use the exec command to call the Linux program called enscript to produce
a postscript file for each of the Tcl files. You should then use the Linux program called
ps2pdf to turn each of the postscript files into a PDF. Finally, merge all of the PDFs using
the Linux program called gs (short for ghostscript). Invoke ghostscript using the following
command
Some documentation
Tcl documentation
the exec man page
Tcl tutorial
the wiki entry for exec

Tcl: Get stdout from exec bash

Within tclsh I can run the following and get the expected output:
% exec bash -c "ulimit -v"
50331648
However within a Tcl script nothing is returned. No error, no output, nothing. There's clearly some gotcha with exec'ing 'bash -c' that I can't work out.
Alternatively, is there a native way in Tcl that I can get the system's memory limit to avoid having to do it this way in the first place?
In an interactive tclsh session, the REPL helpfully prints the output of commands/expressions. That's not the case in a non-interactive program.
exec returns the output of the command: you just need to capture it with the usual command substitution:
set output [exec bash -c "ulimit -v"]
puts $output
The code that you wrote should work; I can't identify why bash would silently fail to run ulimit -v. Even if the script was running in an environment where that was privileged information (why!?) one would still expect to get an error message of some form. That's a very weird problem!
Tcl's base command set doesn't expose any access to memory limits, whether for reading or writing. The simplest workaround that doesn't call an external program is the tclbsd package (apparently it mostly works on most other Unixes as well), which exposes a command that should help:
package require BSD
set limit [bsd::rlimit get soft virtual]

Fail to launch tclsh from tclsh

I'm launching multiple tclsh from inside a TCL script to emulate multi threading. However the calls all fail.
I've simplified the problem down to a test where a TCL proc launches a tclsh.
proc launch_tcl {} {
set cmd "tclsh script.tcl"
set pid [ eval $cmd & ]
}
This produces : invalid command name "tclsh"
I can give following lines on the TCL prompt and they work fine.
set cmd "tclsh script.tcl"
set pid [ eval $cmd & ]
I have tried tclsh with full path to the binary as well with same failure.
Why does the same commands fail inside the proc?
Thanks,
Gert
While Tcl syntax looks more related to shell syntax like bash or tcsh tcl is actually more closely related to Perl or PHP or Ruby. Tcl only interprets tcl code thus just typing the name of another executable does not launch that executable*.
Just like Perl or Ruby (or indeed C and C++) tcl does indeed have mechanisms to launch executables. For that you need the exec command:
exec tclsh script.tcl
Warning on how exec works:
Unlike other languages where the command to spawn external binaries accept a string, tcl's exec is more closely related to C or javascript in that it accepts structured data. If you try to do this:
exec "tclsh script.tcl"
You'll get an error complaining "tclsh script.tcl" does not exist. It will look for an executable called "tclsh script.tcl" (because unix, from the very beginning, allows program names to contain spaces). Instead you need to pass the program name and each individual argument separately. Similarly if you tried:
exec tclsh "arg1 arg2"
The exec command will execute tclsh and pass the string "arg1 arg2" as the first argument.
See the documentation of exec for more info: https://www.tcl.tk/man/tcl/TclCmd/exec.htm
What to do if you have a command in a string?
Don't store commands in strings. There's no real safe way to separate arguments in strings in tcl. Instead compose your command as a list and then use the {*} operator to expand the list when you call exec:
set cmd [list tclsh script.tcl]
exec {*}$cmd
*Note: There may be confusion about this because in interactive mode tcl DOES indeed launch executables if you type it. But this is only a feature of interactive mode.

How do I run interactive shell such as "exec tclsh" from inside tclsh?

Is it possible to run some interactive shell from inside tclsh? Obviously it's easy to run an interactive shell such as bash or tclsh from inside bash, but I have not found a way to do the reverse.
If I run "exec tclsh" or "exec bash" from inside tclsh I don't get a prompt until I type "exit" and hit enter, or I use "ctrl-C" which kills the parent tclsh.
I would prefer not to use an external package, if at all possible.
Provided you don't want to pass values (other than the exit code) back to the calling Tcl code you can do it pretty easily by redirecting the standard channels so that Tcl doesn't capture them:
exec tclsh <#stdin >#stdout 2>#stderr
This will work for pretty much any subprocess (I've just tested it with vi) and is what tclsh actually does magically for you in interactive mode if it decides to try running a subprocess.
If you want to do anything more complex than that, you probably need to look into using Expect as there's a very long list of tricky gotchas otherwise.

Why do OpenShift action hook scripts fail with strange errors?

I renamed an action_hook from a non-cartridge-specific action hook (such as post_restart) to be cartridge-specific (such as post_restart_cron) and then encountered strange new errors such as:
/var/lib/openshift/${USER}/app-root/runtime/repo/.openshift/action_hooks/post_restart_cron: line 5: `firstcron-secondcron': not a valid identifier
The script file post_restart_cron is:
#!/bin/bash
function firstcron-secondcron {
echo in function
}
The issue is that non-cartridge-specific action hooks apparently run bash in non-POSIX mode which allows hyphens in function names, but cartridge-specific action hooks run bash in POSIX mode which does not allow hyphens in function names.
Why do cartridge-specific action hooks run bash in POSIX mode? I'm not 100% sure, but I think the following occurs:
v2_cart_model.rb:cartridge_hooks remembers the hook as source <hook filepath>.
v2_cart_model.rb:do_control_with_directory creates a command string set -e; <path to control script> <action> <other args>; source <hook filepath>.
It probably passes that string to sh -c which runs in POSIX mode and because it uses source, it reads the script directly instead of running it in a new process (which would read the #!/bin/bash line and run /bin/bash which is by default non-POSIX mode).
There must be something different about the non-cartridge-specific codepath in v2_cart_model.rb that avoids the steps above.
My solution was to use unset POSIXLY_CORRECT in my script which disabled POSIX mode.
I debugged this issue by running the set command in my script which showed a variety of bash variables leading to investigations that I used the process of elimination on.