I was wondering: when we use \immediate\write18{...} to run commands in an external shell from Latex, is the \write18 a blocking call or not? In other words, will it wait for the external code to complete and return (with success, or not) - or will Latex just initiate execution in a separate thread in a non-blocking manner, and continue immediately afterwards?

Can an example be supplied that illustrates this?

  • 1
    As far as I know, (La)TeX has absolutely no support for multi-thread or multi-core setups. Anyway, in this specific case, how would you expect the asynchronous protocol to behave? The external command is supposed to have an impact on what TeX does on the rest of the document... – T. Verron Jun 20 '14 at 7:08
  • Thanks for the comment, @T.Verron - I didn't really have much of an expectation either way; I just couldn't track down an explicit mention quickly, forcing me to write a test case - so I thought it would be better to post it :) Cheers! – sdaau Jun 20 '14 at 7:29

The documentation for Web2c explains the shell escape feature for TeX engines based on Web2c, e.g. TeX Live. Section "4.5 Shell escapes":

The shell-command string is passed to the command shell (via the C library function system). The output of shell-command is not diverted anywhere, so it will not appear in the log file, or anywhere but the terminal output. The exit status of the system call is also not available to TeX.

The system function is explained in its manual page:

system() returns after the command has been completed.

Thus system is a "blocking call". That allows that the command creates a file that can be read after the \write in LaTeX, for example. Otherwise it would not be possible, because there is no interface for checking the status of the child (no wait, ...); it could happen that LaTeX finishes \end{document}, before the command execution is finished.

In short:

  • system is a "blocking call"
  • Be aware of the difference \immediate\write18 and \write18 without immediate. The latter execution is deferred to the next page shipout.
  • Keep in mind security. There are options to control, whether commands can be executed. The new restricted mode (enabled by default) has further restrictions.
  • Many thanks for that, @HeikoOberdiek - wasn't aware this was already in the docs; great to have this as reference. Cheers! – sdaau Jun 20 '14 at 7:30
  • I used a write18 shell script to write to an external file, and then downstream in the tex file I \input that external file I have just written. But the result is that the \input got the contents of the external file BEFORE the script's execution. This seems to suggest non-blocking behavior. Any clue why this happens? – Yan King Yin Aug 15 '15 at 16:40
  • 1
    @YanKingYin There are too many unknown variables as the operating system, the TeX port, the script, ... – Heiko Oberdiek Aug 15 '15 at 17:11
  • Yes, seems that my TexMaker cannot make system calls. Not a problem with \write18. – Yan King Yin Aug 15 '15 at 17:19
  • @YanKingYin Option -shell-escape enables \write18. Restricted shell escape is often enabled by default, but this cannot be used for arbitrary scripts. – Heiko Oberdiek Aug 15 '15 at 17:22

Here is a self-answer, as I didn't find this info anywhere explicitly - \immediate\write18 seems to have a blocking behavior.

I used perl on Linux, with a for loop with a sleep inside as the test program; it turns out, it's a bit itchy to escape the linefeed \n as raw ASCII for the external shell command - so, one needs to be careful about that. Otherwise, the MWE below produces the following output, when Latex is called with -shell-escape:

$ pdflatex -shell-escape test.tex 
This is pdfTeX, Version 3.1415926-2.3-1.40.12 (TeX Live 2011)
 \write18 enabled.
entering extended mode
LaTeX2e <2011/06/27>
Babel <v3.8m> and hyphenation patterns for english, dumylang, nohyphenation, lo
Document Class: article 2007/10/19 v1.4h Standard LaTeX document class
Check: outputting command: 
 echo 'for($ix=0;$ix<5;$ix++){ print("perl $ix \n");sleep 1;};' ; perl -e 'for(
$ix=0;$ix<5;$ix++){ print("perl $ix \n");sleep 1;};' 

 \LaTeX typeout: Before write18 

for($ix=0;$ix<5;$ix++){ print("perl $ix 
");sleep 1;};
perl 0 
perl 1 
perl 2 
perl 3 
perl 4 

 \LaTeX typeout: After write18 

(./test.aux) )
No pages of output.
Transcript written on test.log.

Clearly, the \write18 waited for the perl code to execute first - before allowing Latex to continue onwards.

Here is the MWE, test.tex:



% http://tex.stackexchange.com/a/69294/2595
% must escape backslash like this, to have escaped linefeed  
% character \n in \write18 output for the shell (raw ASCII)!
  \catcode `~=11
  \catcode `\|=0
  \catcode `\\=11
  |gdef|LF{\n} % \LF becomes (ASCII) "\n" (verbatim char!)
  |gdef|n{\n} % \n becomes (ASCII) "\n" (verbatim char!)
  |gdef|ELF{\\n} % \ELF becomes (ASCII) "\\n" - escaped backslash for shell!

  %echo 'for($ix=0;$ix<5;$ix++){ print("perl $ix \LF");sleep 1;};'
  % below works, with a previous definition of linefeed (with
  % non-escaped backslash) \n as macro; then linefeed char is verbatim
  %echo 'for($ix=0;$ix<5;$ix++){ print("perl $ix \n");sleep 1;};'
  % like this for escaped-backslash-linefeed to propagate through shell;
  % the programs see escaped backslash then:
  %echo 'for($ix=0;$ix<5;$ix++){ print("perl $ix \ELF");sleep 1;};' ;
  % The proper one is with \n/\LF; don't forget `;` separators for bash
  % (as latex will compact these in a single line anyway)
  echo 'for($ix=0;$ix<5;$ix++){ print("perl $ix \n");sleep 1;};' ;
  perl -e 'for($ix=0;$ix<5;$ix++){ print("perl $ix \n");sleep 1;};'

  Check: outputting command: ^^J
  \cmd% OK

\typeout{^^J \LaTeX typeout: Before write18 ^^J}

  \cmd% OK

\typeout{^^J \LaTeX typeout: After write18 ^^J}


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.