Next: , Previous: Common Shell Constructs, Up: Programming in M4sh


9.2 Support for indirect variable names

Often, it is convenient to write a macro that will emit shell code operating on a shell variable. The simplest case is when the variable name is known. But a more powerful idiom is writing shell code that can work through an indirection, where another variable or command substitution produces the name of the variable to actually manipulate. M4sh supports the notion of polymorphic shell variables, making it easy to write a macro that can deal with either literal or indirect variable names and output shell code appropriate for both use cases. Behavior is undefined if expansion of an indirect variable does not result in a literal variable name.

— Macro: AS_LITERAL_IF (expression, [if-literal], [if-not])

If the expansion of expression is definitely a shell literal, expand if-literal. If the expansion of expression looks like it might contain shell indirections (such as $var or `expr`), then if-not is expanded. In order to reduce the time spent deciding whether an expression is literal, the implementation is somewhat conservative (for example, ‘'[$]'’ is a single-quoted shell literal, but causes if-not to be expanded). While this macro is often used for recognizing shell variable names, it can also be used in other contexts.

          AC_DEFUN([MY_ACTION],
          [AS_LITERAL_IF([$1],
          [echo "$1"],
          [AS_VAR_COPY([tmp], [$1])
          echo "$tmp"])])
— Macro: AS_VAR_APPEND (var, text)

Emit shell code to append the shell expansion of text to the end of the current contents of the polymorphic shell variable var, taking advantage of shells that provide the ‘+=’ extension for more efficient scaling.

For situations where the final contents of var are relatively short (less than 256 bytes), it is more efficient to use the simpler code sequence of var=${var}text (or its polymorphic equivalent of AS_VAR_COPY([tmp], [var]) and AS_VAR_SET([var], ["$tmp"text])). But in the case when the script will be repeatedly appending text into var, issues of scaling start to become apparent. A naive implementation requires execution time linear to the length of the current contents of var as well as the length of text for a single append, for an overall quadratic scaling with multiple appends. This macro takes advantage of shells which provide the extension var+=text, which can provide amortized constant time for a single append, for an overall linear scaling with multiple appends. Note that unlike AS_VAR_SET, this macro requires that text be quoted properly to avoid field splitting and file name expansion.

— Macro: AS_VAR_ARITH (var, expression)

Emit shell code to compute the arithmetic expansion of expression, assigning the result as the contents of the polymorphic shell variable var. The code takes advantage of shells that provide ‘$(())’ for fewer forks, but uses expr as a fallback. Therefore, the syntax for a valid expression is rather limited: all operators must occur as separate shell arguments and with proper quoting, there is no portable equality operator, all variables containing numeric values must be expanded prior to the computation, all numeric values must be provided in decimal without leading zeroes, and the first shell argument should not be a negative number. In the following example, this snippet will print ‘(2+3)*4 == 20’.

          bar=3
          AS_VAR_ARITH([foo], [\( 2 + $bar \) \* 4])
          echo "(2+$bar)*4 == $foo"
— Macro: AS_VAR_COPY (dest, source)

Emit shell code to assign the contents of the polymorphic shell variable source to the polymorphic shell variable dest. For example, executing this M4sh snippet will output ‘bar hi’:

          foo=bar bar=hi
          AS_VAR_COPY([a], [foo])
          AS_VAR_COPY([b], [$foo])
          echo "$a $b"

When it is necessary to access the contents of an indirect variable inside a shell double-quoted context, the recommended idiom is to first copy the contents into a temporary literal shell variable.

          for header in stdint_h inttypes_h ; do
            AS_VAR_COPY([var], [ac_cv_header_$header])
            echo "$header detected: $var"
          done
— Macro: AS_VAR_IF (var, [value], [if-equal], [if-not-equal])

Output a shell conditional statement. If the contents of the polymorphic shell variable var match the string value, execute if-equal; otherwise execute if-not-equal. Avoids shell bugs if an interrupt signal arrives while a command substitution in var is being expanded.

— Macro: AS_VAR_PUSHDEF (m4-name, value)
— Macro: AS_VAR_POPDEF (m4-name)

A common M4sh idiom involves composing shell variable names from an m4 argument (for example, writing a macro that uses a cache variable). value can be an arbitrary string, which will be transliterated into a valid shell name by AS_TR_SH. In order to access the composed variable name based on value, it is easier to declare a temporary m4 macro m4-name with AS_VAR_PUSHDEF, then use that macro as the argument to subsequent AS_VAR macros as a polymorphic variable name, and finally free the temporary macro with AS_VAR_POPDEF. These macros are often followed with dnl, to avoid excess newlines in the output.

Here is an involved example, that shows the power of writing macros that can handle composed shell variable names:

          m4_define([MY_CHECK_HEADER],
          [AS_VAR_PUSHDEF([my_Header], [ac_cv_header_$1])dnl
          AS_VAR_IF([my_Header], [yes], [echo "header $1 available"])dnl
          AS_VAR_POPDEF([my_Header])dnl
          ])
          MY_CHECK_HEADER([stdint.h])
          for header in inttypes.h stdlib.h ; do
            MY_CHECK_HEADER([$header])
          done

In the above example, MY_CHECK_HEADER can operate on polymorphic variable names. In the first invocation, the m4 argument is stdint.h, which transliterates into a literal stdint_h. As a result, the temporary macro my_Header expands to the literal shell name ‘ac_cv_header_stdint_h’. In the second invocation, the m4 argument to MY_CHECK_HEADER is $header, and the temporary macro my_Header expands to the indirect shell name ‘$as_my_Header’. During the shell execution of the for loop, when ‘$header’ contains ‘inttypes.h’, then ‘$as_my_Header’ contains ‘ac_cv_header_inttypes_h’. If this script is then run on a platform where all three headers have been previously detected, the output of the script will include:

          header stdint.h detected
          header inttypes.h detected
          header stdlib.h detected
— Macro: AS_VAR_SET (var, [value])

Emit shell code to assign the contents of the polymorphic shell variable var to the shell expansion of value. value is not subject to field splitting or file name expansion, so if command substitution is used, it may be done with ‘`""`’ rather than using an intermediate variable (see Shell Substitutions). However, value does undergo rescanning for additional macro names; behavior is unspecified if late expansion results in any shell meta-characters.

— Macro: AS_VAR_SET_IF (var, [if-set], [if-undef])

Emit a shell conditional statement, which executes if-set if the polymorphic shell variable var is set to any value, and if-undef otherwise.

— Macro: AS_VAR_TEST_SET (var)

Emit a shell statement that results in a successful exit status only if the polymorphic shell variable var is set.