[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

13. Macros for running shell commands

There are a few builtin macros in m4 that allow you to run shell commands from within m4.

Note that the definition of a valid shell command is system dependent. On UNIX systems, this is the typical /bin/sh. But on other systems, such as native Windows, the shell has a different syntax of commands that it understands. Some examples in this chapter assume /bin/sh, and also demonstrate how to quit early with a known exit value if this is not the case.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

13.1 Determining the platform

Sometimes it is desirable for an input file to know which platform m4 is running on. GNU m4 provides several macros that are predefined to expand to the empty string; checking for their existence will confirm platform details.

Optional builtin: __gnu__
Optional builtin: __os2__
Optional builtin: os2
Optional builtin: __unix__
Optional builtin: unix
Optional builtin: __windows__
Optional builtin: windows

Each of these macros is conditionally defined as needed to describe the environment of m4. If defined, each macro expands to the empty string. For now, these macros silently ignore all arguments, but in a future release of M4, they might warn if arguments are present.

When GNU extensions are in effect (that is, when you did not use the `-G' option, see section Invoking m4), GNU m4 will define the macro __gnu__ to expand to the empty string.

 
$ m4
__gnu__
⇒
__gnu__(`ignored')
⇒
Extensions are ifdef(`__gnu__', `active', `inactive')
⇒Extensions are active
 
$ m4 -G
__gnu__
⇒__gnu__
__gnu__(`ignored')
⇒__gnu__(ignored)
Extensions are ifdef(`__gnu__', `active', `inactive')
⇒Extensions are inactive

On UNIX systems, GNU m4 will define __unix__ by default, or unix when the `-G' option is specified.

On native Windows systems, GNU m4 will define __windows__ by default, or windows when the `-G' option is specified.

On OS/2 systems, GNU m4 will define __os2__ by default, or os2 when the `-G' option is specified.

If GNU m4 does not provide a platform macro for your system, please report that as a bug.

 
define(`provided', `0')
⇒
ifdef(`__unix__', `define(`provided', incr(provided))')
⇒
ifdef(`__windows__', `define(`provided', incr(provided))')
⇒
ifdef(`__os2__', `define(`provided', incr(provided))')
⇒
provided
⇒1

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

13.2 Executing simple commands

Any shell command can be executed, using syscmd:

Builtin: syscmd (shell-command)

Executes shell-command as a shell command.

The expansion of syscmd is void, not the output from shell-command! Output or error messages from shell-command are not read by m4. See section Reading the output of commands, if you need to process the command output.

Prior to executing the command, m4 flushes its buffers. The default standard input, output and error of shell-command are the same as those of m4.

By default, the shell-command will be used as the argument to the `-c' option of the /bin/sh shell (or the version of sh specified by `command -p getconf PATH', if your system supports that). If you prefer a different shell, the configure script can be given the option `--with-syscmd-shell=location' to set the location of an alternative shell at GNU m4 installation; the alternative shell must still support `-c'.

The macro syscmd is recognized only with parameters.

 
define(`foo', `FOO')
⇒
syscmd(`echo foo')
⇒foo
⇒

Note how the expansion of syscmd keeps the trailing newline of the command, as well as using the newline that appeared after the macro.

The following is an example of shell-command using the same standard input as m4:

 
$ echo "m4wrap(\`syscmd(\`cat')')" | m4

It tells m4 to read all of its input before executing the wrapped text, then hand a valid (albeit emptied) pipe as standard input for the cat subcommand. Therefore, you should be careful when using standard input (either by specifying no files, or by passing `-' as a file name on the command line, see section Invoking m4), and also invoking subcommands via syscmd or esyscmd that consume data from standard input. When standard input is a seekable file, the subprocess will pick up with the next character not yet processed by m4; when it is a pipe or other non-seekable file, there is no guarantee how much data will already be buffered by m4 and thus unavailable to the child.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

13.3 Reading the output of commands

If you want m4 to read the output of a shell command, use esyscmd:

Builtin: esyscmd (shell-command)

Expands to the standard output of the shell command shell-command.

Prior to executing the command, m4 flushes its buffers. The default standard input and standard error of shell-command are the same as those of m4. The error output of shell-command is not a part of the expansion: it will appear along with the error output of m4.

By default, the shell-command will be used as the argument to the `-c' option of the /bin/sh shell (or the version of sh specified by `command -p getconf PATH', if your system supports that). If you prefer a different shell, the configure script can be given the option `--with-syscmd-shell=location' to set the location of an alternative shell at GNU m4 installation; the alternative shell must still support `-c'.

The macro esyscmd is recognized only with parameters.

 
define(`foo', `FOO')
⇒
esyscmd(`echo foo')
⇒FOO
⇒

Note how the expansion of esyscmd keeps the trailing newline of the command, as well as using the newline that appeared after the macro.

Just as with syscmd, care must be exercised when sharing standard input between m4 and the child process of esyscmd.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

13.4 Exit status

To see whether a shell command succeeded, use sysval:

Builtin: sysval

Expands to the exit status of the last shell command run with syscmd or esyscmd. Expands to 0 if no command has been run yet.

 
sysval
⇒0
syscmd(`false')
⇒
ifelse(sysval, `0', `zero', `non-zero')
⇒non-zero
syscmd(`exit 2')
⇒
sysval
⇒2
syscmd(`true')
⇒
sysval
⇒0
esyscmd(`false')
⇒
ifelse(sysval, `0', `zero', `non-zero')
⇒non-zero
esyscmd(`exit 2')
⇒
sysval
⇒2
esyscmd(`true')
⇒
sysval
⇒0

sysval results in 127 if there was a problem executing the command, for example, if the system-imposed argument length is exceeded, or if there were not enough resources to fork. It is not possible to distinguish between failed execution and successful execution that had an exit status of 127.

On UNIX platforms, where it is possible to detect when command execution is terminated by a signal, rather than a normal exit, the result is the signal number shifted left by eight bits.

 
dnl This test assumes kill is a shell builtin, and that signals are
dnl recognizable.
ifdef(`__unix__', ,
      `errprint(` skipping: syscmd does not have unix semantics
')m4exit(`77')')dnl
syscmd(`kill -9 $$')
⇒
sysval
⇒2304
syscmd()
⇒
sysval
⇒0
esyscmd(`kill -9 $$')
⇒
sysval
⇒2304

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

13.5 Making temporary files

Commands specified to syscmd or esyscmd might need a temporary file, for output or for some other purpose. There is a builtin macro, mkstemp, for making a temporary file:

Builtin: mkstemp (template)
Builtin: maketemp (template)

Expands to the quoted name of a new, empty file, made from the string template, which should end with the string `XXXXXX'. The six `X' characters are then replaced with random characters matching the regular expression `[a-zA-Z0-9._-]', in order to make the file name unique. If fewer than six `X' characters are found at the end of template, the result will be longer than the template. The created file will have access permissions as if by chmod =rw,go=, meaning that the current umask of the m4 process is taken into account, and at most only the current user can read and write the file.

The traditional behavior, standardized by POSIX, is that maketemp merely replaces the trailing `X' with the process id, without creating a file or quoting the expansion, and without ensuring that the resulting string is a unique file name. In part, this means that using the same template twice in the same input file will result in the same expansion. This behavior is a security hole, as it is very easy for another process to guess the name that will be generated, and thus interfere with a subsequent use of syscmd trying to manipulate that file name. Hence, POSIX has recommended that all new implementations of m4 provide the secure mkstemp builtin, and that users of m4 check for its existence.

The expansion is void and an error issued if a temporary file could not be created.

The macros mkstemp and maketemp are recognized only with parameters.

If you try this next example, you will most likely get different output for the two file names, since the replacement characters are randomly chosen:

 
$ m4
define(`tmp', `oops')
⇒
maketemp(`/tmp/fooXXXXXX')
⇒/tmp/fooa07346
ifdef(`mkstemp', `define(`maketemp', defn(`mkstemp'))',
      `define(`mkstemp', defn(`maketemp'))dnl
errprint(`warning: potentially insecure maketemp implementation
')')
⇒
mkstemp(`doc')
⇒docQv83Uw

Unless you use the `--traditional' command line option (or `-G', see section Invoking m4), the GNU version of maketemp is secure. This means that using the same template to multiple calls will generate multiple files. However, we recommend that you use the new mkstemp macro, introduced in GNU M4 1.4.8, which is secure even in traditional mode. Also, as of M4 1.4.11, the secure implementation quotes the resulting file name, so that you are guaranteed to know what file was created even if the random file name happens to match an existing macro. Notice that this example is careful to use defn to avoid unintended expansion of `foo'.

 
$ m4
define(`foo', `errprint(`oops')')
⇒
syscmd(`rm -f foo-??????')sysval
⇒0
define(`file1', maketemp(`foo-XXXXXX'))dnl
ifelse(esyscmd(`echo \` foo-?????? \''), ` foo-?????? ',
       `no file', `created')
⇒created
define(`file2', maketemp(`foo-XX'))dnl
define(`file3', mkstemp(`foo-XXXXXX'))dnl
ifelse(len(defn(`file1')), len(defn(`file2')),
       `same length', `different')
⇒same length
ifelse(defn(`file1'), defn(`file2'), `same', `different file')
⇒different file
ifelse(defn(`file2'), defn(`file3'), `same', `different file')
⇒different file
ifelse(defn(`file1'), defn(`file3'), `same', `different file')
⇒different file
syscmd(`rm 'defn(`file1') defn(`file2') defn(`file3'))
⇒
sysval
⇒0

[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated on July, 20 2009 using texi2html 1.76.