@menu
* Common Shell Constructs:: Portability layer for common shell constructs
+* Shell Quotation:: Turning text into quoted strings
* Polymorphic Variables:: Support for indirect variable names
* Initialization Macros:: Macros to establish a sane shell environment
* File Descriptor Macros:: File descriptor macros for input and output
Redirections can be placed outside the macro invocation.
@end defmac
-@c We cannot use @dvar because the macro expansion mistreats backslashes.
-@defmac AS_ESCAPE (@var{string}, @r{[}@var{chars} = @samp{`\"$}@r{]})
-@asindex{ESCAPE}
-Expands to @var{string}, with any characters in @var{chars} escaped with
-a backslash (@samp{\}). @var{chars} should be at most four bytes long,
-and only contain characters from the set @samp{`\"$}; however,
-characters may be safely listed more than once in @var{chars} for the
-sake of syntax highlighting editors. The current implementation expands
-@var{string} after adding escapes; if @var{string} contains macro calls
-that in turn expand to text needing shell quoting, you can use
-@code{AS_ESCAPE(m4_dquote(m4_expand([string])))}.
-
-The default for @var{chars} (@samp{\"$`}) is the set of characters
-needing escapes when @var{string} will be used literally within double
-quotes. One common variant is the set of characters to protect when
-@var{string} will be used literally within back-ticks or an unquoted
-here-document (@samp{\$`}). Another common variant is @samp{""}, which can
-be used to form a double-quoted string containing the same expansions
-that would have occurred if @var{string} were expanded in an unquoted
-here-document; however, when using this variant, care must be taken that
-@var{string} does not use double quotes within complex variable
-expansions (such as @samp{$@{foo-`echo "hi"`@}}) that would be broken
-with improper escapes.
-
-This macro is often used with @code{AS_ECHO}. For an example, observe
-the output generated by the shell code generated from this snippet:
-
-@example
-foo=bar
-AS_ECHO(["AS_ESCAPE(["$foo" = ])AS_ESCAPE(["$foo"], [""])"])
-@result{}"$foo" = "bar"
-m4_define([macro], [a, [\b]])
-AS_ECHO(["AS_ESCAPE([[macro]])"])
-@result{}macro
-AS_ECHO(["AS_ESCAPE([macro])"])
-@result{}a, b
-AS_ECHO(["AS_ESCAPE(m4_dquote(m4_expand([macro])))"])
-@result{}a, \b
-@end example
-
-@comment Should we add AS_ESCAPE_SINGLE? If we do, we can optimize in
-@comment the case of @var{string} that does not contain '.
-To escape a string that will be placed within single quotes, use:
-
-@example
-m4_bpatsubst([[@var{string}]], ['], ['\\''])
-@end example
-@end defmac
-
@defmac AS_EXECUTABLE_P (@var{file})
@asindex{EXECUTABLE_P}
Emit code to probe whether @var{file} is a regular file with executable
The GNU C Library}).
@end defmac
+@node Shell Quotation
+@section Shell Quotation
+
+M4sh provides two macros for fabricating shell quoted strings from text.
+
+@defmac AS_QUOTE_S (@var{string})
+@asindex{QUOTE_S}
+Convert @var{string} into a single-quoted shell string.
+@samp{'} within @var{string} will be mapped to @samp{'\''}.
+
+@var{string} is taken literally---it cannot be the contents of a shell
+variable. M4 macros within @var{string} will be expanded, once, before
+quotation.
+@end defmac
+
+
+@defmac AS_QUOTE_D (@var{string}, @ovar{interpolation})
+@asindex{QUOTE_D}
+
+Convert @var{string} into a double-quoted shell string, escaping
+special characters with @samp{\} as necessary. @var{string} is taken
+literally---it cannot be the contents of a shell variable. M4 macros
+within @var{string} will be expanded, once, before quotation.
+
+The @var{interpolation} argument controls whether anything can be
+interpolated into the string when the shell script runs. By default, or
+when this argument is empty, nothing will be interpolated. The possible
+non-empty values for @var{interpolation} are:
+
+@table @code
+@item allow-vars
+Allow variables and arithmetic expressions to be interpolated.
+For example:
+@smallexample
+@group
+AWK=gawk
+echo AS_QUOTE_D([checking whether $AWK supports 'asorti'],
+ [allow-vars])
+ @expansion{} AWK=gawk
+ @expansion{} echo "checking whether $AWK supports 'asorti'"
+ @result{} checking whether gawk supports 'asorti'
+@end group
+@end smallexample
+@noindent
+Without @samp{allow-vars}, the output would instead be @samp{checking
+whether $AWK supports 'asorti'}.
+
+@strong{Compatibility note:} Arithmetic expressions are part of the
+POSIX shell standard, but are not universally implemented; see
+@ref{shell-arithmetic,, the discussion of @samp{$((@var{expression}))}}
+in @ref{Shell Substitutions}. In Autoconf scripts it is better to use
+@code{AS_VAR_ARITH} and then interpolate the variable holding the
+result.
+
+@item allow-commands
+Allow execution of embedded commands, written either with
+@samp{`@var{command}`} or with @samp{$(@var{command})}. (Even today,
+not all shells implement @samp{$(@var{command})}, so truly portable
+scripts still need to avoid it; but @emph{defensive} quotation requires
+us to assume that it @emph{does} work.)
+
+@strong{Caution:} Embedded commands that contain nested double-quoted
+strings will be mangled (handling them correctly is beyond M4's parsing
+capabilities). Instead, assign the result of the command to a shell
+variable and use variable interpolation.
+
+@item allow-vars,allow-commands
+@itemx allow-commands,allow-vars
+Allow both of the above kinds of interpolation.
+@end table
+@end defmac
+
+
@node Polymorphic Variables
@section Support for indirect variable names
@cindex variable name indirection
@end example
+@anchor{shell-arithmetic}
@item $((@var{expression}))
@cindex @code{$((@var{expression}))}
Arithmetic expansion is not portable as some shells (most
integrated into @code{AC_PROG_LEX} (@pxref{AC_PROG_LEX}).
@end defmac
+
+@defmac AS_ESCAPE (@var{string}, @ovar{chars})
+@asindex{ESCAPE}
+This macro has been superseded by @code{AS_QUOTE_D} and @code{AS_QUOTE_S}
+(@pxref{Shell Quotation}). @command{autoupdate} does not attempt to
+update uses of @code{AS_ESCAPE}, because the update will usually require
+modifying surrounding code, which @command{autoupdate} cannot do.
+
+Like @code{AS_QUOTE_D}, @code{AS_ESCAPE} escapes special characters for
+use inside shell double-quoted strings. However, it is harder to use
+correctly: it does not put quotation marks around @var{string}, it does
+not expand M4 macros in @var{string} before escaping special characters,
+and many of the values of the @var{chars} argument will produce
+improperly quoted results for some strings.
+
+@samp{"AS_ESCAPE(m4_dquote(m4_expand([string])))"} is completely
+equivalent to @samp{AS_QUOTE_D([string])}. Watch out for the
+surrounding quotation marks! They were necessary with @code{AS_ESCAPE}
+but become incorrect with @code{AS_QUOTE_D}.
+
+In most cases, it is also correct to replace
+@samp{"AS_ESCAPE([string])"}, without the pre-expansion, with
+@samp{AS_QUOTE_D([string])}. If @var{string} should not be expanded
+at all, use either @samp{AS_QUOTE_D([[string]])} or
+@samp{AS_QUOTE_D(m4_dquote([string]))}
+as appropriate.
+
+If you actually need M4 macros within @var{string} to be expanded
+and any special characters from the expansion @emph{not} to be escaped,
+split up the string like this:
+@samp{AS_QUOTE_D([prefix])"macro"AS_QUOTE_D([suffix])}. This will
+produce @samp{"prefix""expanded text""suffix"} in the output; the
+doubled quote marks will be removed by the shell.
+
+Uses of @code{AS_ESCAPE} with the @var{chars} argument fall into
+four classes, as shown in the table below:
+
+@c Without the trailing hard spaces on the first line,
+@c the HTML render has no whitespace between the columns!
+@multitable {@code{`$"\@ @ `$"@ @ `$\@ @ `$@ @ @ }} {@samp{AS_QUOTE_D([...], [allow-commands,allow-vars])}}
+@headitem @var{chars} arg @tab Replace with@dots{}
+@item
+@t{`$"\@ @ `$"@ @ `$\@ @ `$@ @ @ }
+@tab
+@samp{AS_QUOTE_D([...])}
+@item
+@t{@ `"\@ @ @ `"@ @ @ `\@ @ @ `}
+@tab
+@samp{AS_QUOTE_D([...], [allow-vars])}
+@item
+@t{@ $"\@ @ @ $"@ @ @ $\@ @ @ $}
+@tab
+@samp{AS_QUOTE_D([...], [allow-commands])}
+@item
+@t{@ @ "\@ @ @ @ "@ @ @ @ \}
+@tab
+@samp{AS_QUOTE_D([...], [allow-commands,allow-vars])}
+@end multitable
+
+The order of characters within @var{chars} is not significant, and
+duplicate characters are ignored: @samp{"`} and @samp{`""} are both the
+same as @samp{`"} and should be mapped to @code{allow-vars}.
+
+Uses of @code{AS_ESCAPE} with any characters in @var{chars} besides
+@samp{"}, @samp{`}, @samp{$}, and/or @samp{\} are incorrect, and have no
+@code{AS_QUOTE_D} equivalent. In particular, @code{AS_ESCAPE} cannot
+generate @emph{single-quoted} strings (but @code{AS_QUOTE_S} can).
+
+@strong{Compatibility Note:} Prior to Autoconf 2.73, the various
+possibilities for @var{chars} on each row of the table above were not
+equivalent. The behavior of older @code{AS_ESCAPE} only matches the
+behavior of @code{AS_QUOTE_D} for the first possibility listed in each
+row.
+
+When @code{AS_ESCAPE} is used for its intended purpose of generating
+double-quoted shell strings, it is a @emph{bug} to use any of the other
+possibilities: they would cause it not to quote all @var{string}s
+correctly. Therefore, in Autoconf 2.73 and later, @code{AS_ESCAPE}
+treats all the possibilities on each row as equivalent to the first.
+If you were using @code{AS_ESCAPE} off-label and really wanted the
+old behavior of @samp{AS_ESCAPE([@var{string}], [`$])} (for example),
+change it to @samp{m4_bpatsubst([@var{string}], [[`$]], [\\\&])}.
+@end defmac
+
@node Autoconf 1
@section Upgrading From Version 1
@cindex Upgrading autoconf
# Quote for Perl '' strings, which are those used by Autoheader.
m4_define([AH_VERBATIM],
[AS_LITERAL_WORD_IF([$1],
- [AH_OUTPUT(_m4_expand([$1]), AS_ESCAPE([[$2]], [\']))])])
+ [AH_OUTPUT(_m4_expand([$1]), m4_bpatsubst([[$2]], [[\']], [\\\&]))])])
# AH_TEMPLATE(KEY, DESCRIPTION)
# Append the pre-expanded STRING and a newline to confdefs.h, as if by
# a quoted here-doc.
m4_define([_AC_DEFINE],
-[AS_ECHO(["AS_ESCAPE([[$1]])"]) >>confdefs.h])
+[AS_ECHO([AS_QUOTE_D([[$1]])]) >>confdefs.h])
# AC_DEFINE_UNQUOTED(VARIABLE, [VALUE], [DESCRIPTION])
# avoid AS_ECHO if "#" is present to avoid confusing m4 with comments,
# but quadrigraphs are fine in that case.
m4_define([_AC_DEFINE_UNQUOTED],
-[m4_if(m4_bregexp([$1], [#\|\\\|`\|\(\$\|@S|@\)\((|{|@{:@\)]), [-1],
- [AS_ECHO(["AS_ESCAPE([$1], [""])"]) >>confdefs.h],
+[m4_bmatch([$1], [#\|\\\|`\|\(\$\|@S|@\)\((|{|@{:@\)],
[cat >>confdefs.h <<_ACEOF
[$1]
-_ACEOF])])
+_ACEOF],
+ [AS_ECHO([AS_QUOTE_D([$1])]) >>confdefs.h])])
# _AC_DEFINE_Q(MACRO, VARIABLE, [VALUE], [DESCRIPTION])
# -------------------
AC_DEFUN([AC_RUN_LOG],
[_AC_RUN_LOG([$1],
- [AS_ECHO(["$as_me:${as_lineno-$LINENO}: AS_ESCAPE([$1])"])])])
+ [AS_ECHO(["$as_me:${as_lineno-$LINENO}: "AS_QUOTE_D([$1])])])])
[AC_REQUIRE([_AC_UNDECLARED_BUILTIN_]_AC_LANG_ABBREV)]dnl
[AS_VAR_PUSHDEF([ac_Symbol], [ac_cv_have_decl_$1])]dnl
[ac_fn_check_decl ]dnl
-["$LINENO" "$1" "ac_Symbol" "AS_ESCAPE([AC_INCLUDES_DEFAULT([$4])], [""])" ]dnl
+["$LINENO" "$1" "ac_Symbol" ]dnl
+[AS_QUOTE_D([AC_INCLUDES_DEFAULT([$4])], [allow-commands,allow-vars]) ]dnl
["$ac_[]_AC_LANG_ABBREV[]_undeclared_builtin_options" "_AC_LANG_PREFIX[]FLAGS"]
[AS_VAR_IF([ac_Symbol], [yes], [$2], [$3])]dnl
[AS_VAR_POPDEF([ac_Symbol])]dnl
be computed])],
[_$0_BODY])]dnl
[AS_IF([ac_fn_[]_AC_LANG_ABBREV[]_compute_int "$LINENO" "$2" "$1" ]dnl
- ["AS_ESCAPE([$3], [""])"],
+ [AS_QUOTE_D([$3], [allow-commands,allow-vars])],
[], [$4])
])# AC_COMPUTE_INT
[_AC_CHECK_HEADER_COMPILE_FN()]dnl
[AS_VAR_PUSHDEF([ac_Header], [ac_cv_header_$1])]dnl
[ac_fn_[]_AC_LANG_ABBREV[]_check_header_compile ]dnl
-["$LINENO" "$1" "ac_Header" "AS_ESCAPE([AC_INCLUDES_DEFAULT([$4])], [""])"
+["$LINENO" "$1" "ac_Header" ]dnl
+[AS_QUOTE_D([AC_INCLUDES_DEFAULT([$4])], [allow-commands,allow-vars])
AS_VAR_IF([ac_Header], [yes], [$2], [$3])
AS_VAR_POPDEF([ac_Header])])# _AC_CHECK_HEADER_COMPILE
[$0_BODY])]dnl
[AS_VAR_PUSHDEF([ac_Type], [ac_cv_type_$1])]dnl
[ac_fn_[]_AC_LANG_ABBREV[]_check_type "$LINENO" "$1" "ac_Type" ]dnl
-["AS_ESCAPE([AC_INCLUDES_DEFAULT([$4])], [""])"
+[AS_QUOTE_D([AC_INCLUDES_DEFAULT([$4])], [allow-commands,allow-vars])
AS_VAR_IF([ac_Type], [yes], [$2], [$3])
AS_VAR_POPDEF([ac_Type])dnl
])# _AC_CHECK_TYPE_NEW
[AS_VAR_PUSHDEF([ac_Member], [ac_cv_member_$1])]dnl
[ac_fn_[]_AC_LANG_ABBREV[]_check_member "$LINENO" ]dnl
[m4_bpatsubst([$1], [^\([^.]*\)\.\(.*\)], ["\1" "\2"]) "ac_Member" ]dnl
-["AS_ESCAPE([AC_INCLUDES_DEFAULT([$4])], [""])"
+[AS_QUOTE_D([AC_INCLUDES_DEFAULT([$4])], [allow-commands,allow-vars])
AS_VAR_IF([ac_Member], [yes], [$2], [$3])
AS_VAR_POPDEF([ac_Member])dnl
])# AC_CHECK_MEMBER
# _AT_LINE_ESCAPED
# ----------------
# Same as AT_LINE, but already escaped for the shell.
-m4_define([_AT_LINE_ESCAPED], ["AS_ESCAPE(m4_dquote(AT_LINE))"])
+m4_define([_AT_LINE_ESCAPED], [AS_QUOTE_D([AT_LINE])])
# _AT_NORMALIZE_TEST_GROUP_NUMBER(SHELL-VAR)
# numerical order.
at_format='m4_bpatsubst(m4_defn([AT_ordinal]), [.], [?])'
# Description of all the test groups.
-at_help_all="AS_ESCAPE(m4_dquote(m4_defn([AT_help_all])))"
+at_help_all=AS_QUOTE_D(m4_defn([AT_help_all]))
# List of the all the test groups.
at_groups_all=`AS_ECHO(["$at_help_all"]) | sed 's/;.*//'`
m4_divert_push([TEST_GROUPS])dnl
[#AT_START_]AT_ordinal
at_fn_group_banner AT_ordinal 'm4_defn([AT_line])' \
- "AS_ESCAPE(m4_dquote(m4_defn([AT_description])))" m4_format(["%*s"],
+ AS_QUOTE_D(m4_dquote(m4_defn([AT_description]))) m4_format(["%*s"],
m4_max(0, m4_eval(47 - m4_qlen(m4_defn([AT_description])))), [])m4_if(
AT_banner_ordinal, [0], [], [ AT_banner_ordinal])
m4_ifset([AT_prepare_each_test], [AT_prepare_each_test
m4_divert_text([BANNERS],
[@%:@ Banner AT_banner_ordinal. AT_LINE
@%:@ Category starts at test group m4_incr(AT_ordinal).
-at_banner_text_[]AT_banner_ordinal="AS_ESCAPE([$1])"])dnl
+at_banner_text_[]AT_banner_ordinal=AS_QUOTE_D([$1])])dnl
])# AT_BANNER
# This may cause spurious failures when the test suite is run with '-x'.
#
_AT_DEFINE_SETUP([AT_CHECK],
-[_AT_CHECK(m4_expand([$1]), [$2], AS_ESCAPE(m4_dquote(m4_expand([$3]))),
- AS_ESCAPE(m4_dquote(m4_expand([$4]))), [$5], [$6])])
+[_AT_CHECK(m4_expand([$1]), [$2],
+ AS_QUOTE_D([$3]),
+ AS_QUOTE_D([$4]),
+ [$5], [$6])])
# AT_CHECK_UNQUOTED(COMMANDS, [STATUS = 0], STDOUT, STDERR,
# [RUN-IF-FAIL], [RUN-IF-PASS])
# ---------------------------------------------------------
-# Like AT_CHECK, but do not AS_ESCAPE shell metacharacters in the STDOUT
+# Like AT_CHECK, but perform string interpolations in the STDOUT
# and STDERR arguments before running the comparison.
_AT_DEFINE_SETUP([AT_CHECK_UNQUOTED],
-[_AT_CHECK(m4_expand([$1]), [$2], AS_ESCAPE(m4_dquote(m4_expand([$3])), [""]),
- AS_ESCAPE(m4_dquote(m4_expand([$4])), [""]), [$5], [$6])])
+[_AT_CHECK(m4_expand([$1]), [$2],
+ AS_QUOTE_D([$3], [allow-commands,allow-vars]),
+ AS_QUOTE_D([$4], [allow-commands,allow-vars]),
+ [$5], [$6])])
# AT_CHECK_NOESCAPE(COMMANDS, [STATUS = 0], STDOUT, STDERR,
# [RUN-IF-FAIL], [RUN-IF-PASS])
dnl We know at build time that tracing COMMANDS is always safe.
[[at_fn_check_prepare_trace],]dnl
dnl COMMANDS may contain parameter expansions; expand them at runtime.
-[[at_fn_check_prepare_dynamic "AS_ESCAPE([[$1]], [`\"])"])[]]dnl
+[[at_fn_check_prepare_dynamic AS_QUOTE_D([[$1]], [allow-vars])])[]]dnl
[_m4_popdef([at_reason])])
# -----------------------------
# These are subroutines of AT_CHECK. Using indirect dispatch is a tad
# faster than using m4_case, and these are called very frequently.
-m4_define([AT_DIFF_STDERR(stderr)],
+m4_define([AT_DIFF_STDERR("stderr")],
[echo stderr:; tee stderr <"$at_stderr"])
-m4_define([AT_DIFF_STDERR(stderr-nolog)],
+m4_define([AT_DIFF_STDERR("stderr-nolog")],
[echo stderr captured; cp "$at_stderr" stderr])
-m4_define([AT_DIFF_STDERR(ignore)],
+m4_define([AT_DIFF_STDERR("ignore")],
[echo stderr:; cat "$at_stderr"])
-m4_define([AT_DIFF_STDERR(ignore-nolog)])
-m4_define([AT_DIFF_STDERR(experr)],
+m4_define([AT_DIFF_STDERR("ignore-nolog")])
+m4_define([AT_DIFF_STDERR("experr")],
[$at_diff experr "$at_stderr" || at_failed=:])
-m4_define([AT_DIFF_STDERR()],
+m4_define([AT_DIFF_STDERR("")],
[at_fn_diff_devnull "$at_stderr" || at_failed=:])
-m4_define([AT_DIFF_STDOUT(stdout)],
+m4_define([AT_DIFF_STDOUT("stdout")],
[echo stdout:; tee stdout <"$at_stdout"])
-m4_define([AT_DIFF_STDOUT(stdout-nolog)],
+m4_define([AT_DIFF_STDOUT("stdout-nolog")],
[echo stdout captured; cp "$at_stdout" stdout])
-m4_define([AT_DIFF_STDOUT(ignore)],
+m4_define([AT_DIFF_STDOUT("ignore")],
[echo stdout:; cat "$at_stdout"])
-m4_define([AT_DIFF_STDOUT(ignore-nolog)])
-m4_define([AT_DIFF_STDOUT(expout)],
+m4_define([AT_DIFF_STDOUT("ignore-nolog")])
+m4_define([AT_DIFF_STDOUT("expout")],
[$at_diff expout "$at_stdout" || at_failed=:])
-m4_define([AT_DIFF_STDOUT()],
+m4_define([AT_DIFF_STDOUT("")],
[at_fn_diff_devnull "$at_stdout" || at_failed=:])
# _AT_CHECK(COMMANDS, [STATUS = 0], STDOUT, STDERR,
m4_define([_AT_CHECK],
[m4_define([AT_ingroup])]dnl
[{ set +x
-AS_ECHO(["$at_srcdir/AT_LINE: AS_ESCAPE([[$1]])"])
+AS_ECHO(["$at_srcdir/AT_LINE: "AS_QUOTE_D([[$1]])])
_AT_DECIDE_TRACEABLE([$1]) _AT_LINE_ESCAPED
( $at_check_trace; [$1]
) >>"$at_stdout" 2>>"$at_stderr" AS_MESSAGE_LOG_FD>&-
at_status=$? at_failed=false
$at_check_filter
m4_ifdef([AT_DIFF_STDERR($4)], [m4_indir([AT_DIFF_STDERR($4)])],
- [echo >>"$at_stderr"; AS_ECHO([["$4"]]) | \
+ [echo >>"$at_stderr"; AS_ECHO([[$4]]) | \
$at_diff - "$at_stderr" || at_failed=:])
m4_ifdef([AT_DIFF_STDOUT($3)], [m4_indir([AT_DIFF_STDOUT($3)])],
- [echo >>"$at_stdout"; AS_ECHO([["$3"]]) | \
+ [echo >>"$at_stdout"; AS_ECHO([[$3]]) | \
$at_diff - "$at_stdout" || at_failed=:])
m4_if([$2], [ignore], [at_fn_check_skip],
[at_fn_check_status m4_default([$2], [0])]) $at_status "$at_srcdir/AT_LINE"
[m4_set_map([_AS_DETECT_SUGGESTED_BODY], [_AS_DETECT_SUGGESTED_PRUNE])]dnl
[m4_pushdef([AS_EXIT], [exit m4_default(]m4_dquote([$][1])[, 1)])]dnl
[if test "x$CONFIG_SHELL" = x; then
- as_bourne_compatible="AS_ESCAPE(_m4_expand([_AS_BOURNE_COMPATIBLE]))"
+ as_bourne_compatible=AS_QUOTE_D([_AS_BOURNE_COMPATIBLE])
_AS_DETECT_EXPAND([as_required], [_AS_DETECT_REQUIRED_BODY])
_AS_DETECT_EXPAND([as_suggested], [_AS_DETECT_SUGGESTED_BODY])
AS_IF([_AS_RUN(["$as_required"])],
# shuffle fd's.
m4_define([AS_ORIGINAL_STDIN_FD], [0])
-
-# AS_ESCAPE(STRING, [CHARS = `\"$])
+# AS_QUOTE_S(TEXT)
+# ----------------
+# Quote TEXT for the shell, using single quotes. Expand macros within
+# the text prior to quotation.
+#
+# With GNU m4 1.4.19, avoiding a call to m4_bpatsubst when it would
+# have nothing to do is good for a ~2.5x speedup.
+m4_define([AS_QUOTE_S], ['_$0(m4_dquote(m4_expand([$1])))'])
+m4_define([_AS_QUOTE_S],
+[m4_if([m4_index([$1], ['])], [-1],
+ [$1],
+ [m4_bpatsubst([$1], ['], ['\''])])])
+
+# AS_QUOTE_D(TEXT, [INTERPOLATION])
# ---------------------------------
-# Add backslash escaping to the CHARS in STRING. In an effort to
-# optimize use of this macro inside double-quoted shell constructs,
-# the behavior is intentionally undefined if CHARS is longer than 4
-# bytes, or contains bytes outside of the set [`\"$]. However,
-# repeated bytes within the set are permissible (AS_ESCAPE([$1], [""])
-# being a common way to be nice to syntax highlighting).
-#
-# Avoid the m4_bpatsubst if there are no interesting characters to escape.
-# _AS_ESCAPE bypasses argument defaulting.
+# Quote TEXT for the shell, using double quotes. Expand macros within
+# the text prior to quotation. INTERPOLATION says what kinds of
+# interpolations to allow:
+#
+# - blank/absent: no interpolation
+# - allow-vars: allow interpolation of $var ${var} $((expr))
+# - allow-commands: allow interpolation of `command` $(command)
+# - allow-commands,allow-vars
+# allow-vars,allow-commands
+# allow both the above kinds of interpolation
+# (there is intentionally no 'allow-all' because we want
+# people to be able to find all cases where command interpolation
+# is enabled by grepping for 'allow-commands').
+m4_define([AS_QUOTE_D],
+["_$0_QUOTE(m4_dquote(m4_expand([$1])), _$0_IARGCHECK([$2]))"])
+
+# _AS_QUOTE_D_IARGCHECK(INTERPOLATION)
+# ------------------------------------
+# Evaluate the INTERPOLATION argument to AS_QUOTE_D. If it is valid,
+# return a list of the second, third, fourth, and fifth arguments to
+# _AS_QUOTE_D_QUOTE, otherwise issue an error.
+m4_define([_AS_QUOTE_D_ARGCHECK],
+[m4_case(m4_translit([[$1]], [ ][ ][
+]),
+ [], [[$], [\"`], [$$$], [[\"$`]]],
+ [allow-vars], [[$], [\"`], [$$$], [[\"`]|\$(\($|[^(]\)]],
+ [allow-commands], [[$], [\"], [$$], [[\"]\|\$[a-zA-Z_{]\|\$((]],
+ [allow-commands,allow-vars], [["], [\], ["], [[\"]]],
+ [allow-vars,allow-commands], [["], [\], ["], [[\"]]],
+ [m4_fatal([invalid INTERPOLATION argument '$1' to AS_QUOTE_D])])])
+
+# _AS_QUOTE_D_QUOTE(STRING, C, HARS, CCCC, REGEX)
+# -----------------------------------------------
+# Insert a backslash in STRING before every match for REGEX.
+# C, HARS, and CCCC are hints for a performance hack: if C is not
+# present in STRING after transliterating HARS to CCCC, then
+# REGEX is assumed not to match anywhere within STRING.
+# (As noted for AS_QUOTE_S, avoiding calls to m4_bpatsubst is good
+# for a ~2.5x speedup even when the overhead of the translit and index
+# are accounted for.
+m4_define([_AS_QUOTE_D_QUOTE],
+[m4_if(m4_index(m4_if([$3], [], [$1], [m4_translit([[$1]], [$3], [$4])]), [$2]),
+ [-1],
+ [$1],
+ [m4_bpatsubst([$1], [$5], [\\\&])])])
+
+# AS_ESCAPE(STRING, [CHARS])
+# --------------------------
+# Superseded by AS_QUOTE_D. The differences are:
+# - Does not wrap " ... " around the result.
+# - Does not pre-expand the string.
+# - CHARS is a much clumsier way to specify what interpolation to allow.
+# Almost all existing uses of AS_ESCAPE with a non-default CHARS
+# argument are incorrect per the original implementation.
+# This compat stub attempts to map each possible value of CHARS
+# (plus one more that wasn't supposed to happen at all) to an
+# AS_QUOTE_D/S invocation that will do what was intended.
m4_define([AS_ESCAPE],
-[_$0([$1], m4_if([$2], [], [[`], [\"$]], [m4_substr([$2], [0], [1]), [$2]]))])
-
-# _AS_ESCAPE(STRING, KEY, SET)
-# ----------------------------
-# Backslash-escape all instances of the single byte KEY or up to four
-# bytes in SET occurring in STRING. Although a character can occur
-# multiple times, optimum efficiency occurs when KEY and SET are
-# distinct, and when SET does not exceed two bytes. These particular
-# semantics allow for the fewest number of parses of STRING, as well
-# as taking advantage of the optimizations in m4 1.4.13+ when
-# m4_translit is passed SET of size 2 or smaller.
-m4_define([_AS_ESCAPE],
-[m4_if(m4_index(m4_translit([[$1]], [$3], [$2$2$2$2]), [$2]), [-1],
- [$0_], [m4_bpatsubst])([$1], [[$2$3]], [\\\&])])
-m4_define([_AS_ESCAPE_], [$1])
-
-
-# _AS_QUOTE(STRING)
-# -----------------
-# If there are quoted (via backslash) backquotes, output STRING
-# literally and warn; otherwise, output STRING with ` and " quoted.
-#
-# Compatibility glue between the old AS_MSG suite which did not
-# quote anything, and the modern suite which quotes the quotes.
-# If STRING contains '\\' or '\$', it's modern.
-# If STRING contains '\"' or '\`', it's old.
-# Otherwise it's modern.
-#
-# Profiling shows that m4_index is 5 to 8x faster than m4_bregexp. The
-# slower implementation used:
-# m4_bmatch([$1],
-# [\\[\\$]], [$2],
-# [\\[`"]], [$3],
-# [$2])
-# The current implementation caters to the common case of no backslashes,
-# to minimize m4_index expansions (hence the nested if).
-m4_define([_AS_QUOTE],
-[m4_cond([m4_index([$1], [\])], [-1], [_AS_QUOTE_MODERN],
- [m4_eval(m4_index(m4_translit([[$1]], [$], [\]), [\\]) >= 0)],
-[1], [_AS_QUOTE_MODERN],
- [m4_eval(m4_index(m4_translit([[$1]], ["], [`]), [\`]) >= 0)],dnl"
-[1], [_AS_QUOTE_OLD],
- [_AS_QUOTE_MODERN])([$1])])
-
-m4_define([_AS_QUOTE_MODERN],
-[_AS_ESCAPE([$1], [`], [""])])
-
-m4_define([_AS_QUOTE_OLD],
-[m4_warn([obsolete],
- [back quotes and double quotes must not be escaped in: $1])$1])
+[m4_warn([obsolete], [AS_ESCAPE is deprecated, change to AS_QUOTE_D or _S])]dnl
+dnl Handle the most common case without going through _AS_ESCAPE_MAPCHARS,
+dnl which is expensive.
+dnl AS_ESCAPE was never intended to produce single-quoted strings but
+dnl someone tried to use it that way anyway!
+dnl https://sources.debian.org/src/fakechroot/2.20.1+ds-17/m4/check_func_argtypes.m4/?hl=111#L111
+[m4_case([$2],
+ [], [_AS_QUOTE_D_QUOTE([$1], _AS_QUOTE_D_ARGCHECK([]))],
+ ['], [_AS_QUOTE_S([$1])],
+ [''], [_AS_QUOTE_S([$1])],
+ [_AS_QUOTE_D_QUOTE([$1], _AS_QUOTE_D_ARGCHECK([]))])])
+# [_AS_QUOTE_D_QUOTE([$1], _AS_QUOTE_D_IARGCHECK(_AS_ESCAPE_MAPCHARS([$2])))])])
+
+# The characters could be in any order and they could also be
+# duplicated. M4 + M4sugar does have a "remove duplicate from list"
+# mechanism, but it doesn't have "sort list" at all.
+# m4_define([_AS_ESCAPE_MAPCHARS],
+# [__AS_ESCAPE_MAPCHARS(m4_join_uniq([], m4_bpatsubst([$1], [.], [[\&],])))])
+# m4_define([__AS_ESCAPE_MAPCHARS],
+# [[[$1]] m4_bmatch([$1],
+# [^\([$`][$`]\|[$`"][$`"][$`"]\|[$\`][$\`][$\`]\|[$`\"][$`\"][$`\"][$`\"]\)$],
+# [],
+# [^\([$]\|[\$][\$]\|["$]["$]\|[\"$][\"$][\"$]\)$],
+# [allow-commands],
+# [^\([`]\|[\`][\`]\|["`]["`]\|[\"`][\"`][\"`]\)$],
+# [allow-vars],
+# [^\([\\]|["]|[\"][\"]\)$],
+# [allow-commands,allow-vars],
+# [invalid])])
+# [m4_fatal([invalid CHARS argument '$1' to AS_ESCAPE])])])
# _AS_ECHO_UNQUOTED(STRING, [FD = AS_MESSAGE_FD])
# _AS_ECHO(STRING, [FD = AS_MESSAGE_FD])
# --------------------------------------
-# Protect STRING from backquote expansion, echo the result to FD.
+# Protect STRING from backquote expansion but not variable expansion,
+# echo the result to FD.
m4_define([_AS_ECHO],
-[_AS_ECHO_UNQUOTED([_AS_QUOTE([$1])], [$2])])
+[AS_ECHO([AS_QUOTE_D([$1], [allow-vars])]) >&m4_default([$2], [AS_MESSAGE_FD])])
+
+
+# _AS_ECHO_N(STRING, [FD = AS_MESSAGE_FD])
+# ----------------------------------------
+# Same as _AS_ECHO, but echo doesn't return to a new line.
+m4_define([_AS_ECHO_N],
+[AS_ECHO_N([AS_QUOTE_D([$1], [allow-vars])]) >&m4_default([$2], [AS_MESSAGE_FD])])
# _AS_ECHO_LOG(STRING)
[_AS_ECHO([$as_me:${as_lineno-$LINENO}: $1], AS_MESSAGE_LOG_FD)])
-# _AS_ECHO_N(STRING, [FD = AS_MESSAGE_FD])
-# ----------------------------------------
-# Same as _AS_ECHO, but echo doesn't return to a new line.
-m4_define([_AS_ECHO_N],
-[AS_ECHO_N(["_AS_QUOTE([$1])"]) >&m4_default([$2], [AS_MESSAGE_FD])])
-
-
# AS_MESSAGE(STRING, [FD = AS_MESSAGE_FD])
# ----------------------------------------
# Output "`basename $0`: STRING" to the open file FD, and if logging
m4_defun_init([AS_ERROR],
[m4_append_uniq([_AS_CLEANUP],
[m4_divert_text([M4SH-INIT-FN], [_AS_ERROR_PREPARE[]])])],
-[as_fn_error m4_default([$2], [$?]) "_AS_QUOTE([$1])"m4_ifval(AS_MESSAGE_LOG_FD,
+[as_fn_error m4_default([$2], [$?]) AS_QUOTE_D([$1], [allow-vars])m4_ifval(AS_MESSAGE_LOG_FD,
[ "$LINENO" AS_MESSAGE_LOG_FD])])
# _AS_BOX_LITERAL(MESSAGE, [FRAME-CHARACTER = '-'])
# -------------------------------------------------
m4_define([_AS_BOX_LITERAL],
-[AS_ECHO(["_AS_ESCAPE(m4_dquote(m4_expand([m4_text_box($@)])), [`], [\"$])"])])
-
+[AS_ECHO([AS_QUOTE_D([m4_text_box($@)])])])
# _AS_BOX_INDIR(MESSAGE, [FRAME-CHARACTER = '-'])
# -----------------------------------------------
[pp[]]]m4_dquote(m4_for(,1,255,,[[_]]))[)])
m4_define([_AS_TR_SH_INDIR],
-[`AS_ECHO(["_AS_ESCAPE([[$1]], [`], [\])"]) | sed "$as_sed_sh"`])
+[`AS_ECHO([AS_QUOTE_D([$1], [allow-vars])]) | sed "$as_sed_sh"`])
# _AS_TR_CPP_PREPARE
[P[]]]m4_dquote(m4_defn([m4_cr_LETTERS])m4_for(,1,255,,[[_]]))[)])
m4_define([_AS_TR_CPP_INDIR],
-[`AS_ECHO(["_AS_ESCAPE([[$1]], [`], [\])"]) | sed "$as_sed_cpp"`])
+[`AS_ECHO([AS_QUOTE_D([$1], [allow-vars])]) | sed "$as_sed_cpp"`])
# _AS_TR_PREPARE
VAR. Take advantage of any shell optimizations that allow amortized
linear growth over repeated appends, instead of the typical quadratic
growth present in naive implementations.])
-AS_IF([_AS_RUN(["AS_ESCAPE(m4_quote(_AS_VAR_APPEND_WORKS))"])],
+AS_IF([_AS_RUN([AS_QUOTE_D([_AS_VAR_APPEND_WORKS])])],
[eval 'as_fn_append ()
{
eval $[]1+=\$[]2
[Perform arithmetic evaluation on the ARGs, and store the result in
the global $as_val. Take advantage of shells that can avoid forks.
The arguments must be portable across $(()) and expr.])
-AS_IF([_AS_RUN(["AS_ESCAPE(m4_quote(_AS_VAR_ARITH_WORKS))"])],
+AS_IF([_AS_RUN([AS_QUOTE_D([_AS_VAR_ARITH_WORKS])])],
[eval 'as_fn_arith ()
{
as_val=$(( $[]* ))
m4_define([AS_VAR_GET],
[AS_LITERAL_WORD_IF([$1],
[$$1],
- [`eval 'as_val=${'_AS_ESCAPE([[$1]], [`], [\])'};AS_ECHO(["$as_val"])'`])])
+ [`eval 'as_val=\$$2;AS_ECHO(["$as_val"])'`])])
# AS_VAR_IF(VARIABLE, VALUE, IF-TRUE, IF-FALSE)
[AS_VAR_COPY([as_val], [$1])
AS_IF(m4_ifval([$2], [[test "x$as_val" = x[]$2]], [[${as_val:+false} :]])],
[AS_IF(m4_ifval([$2],
- [[eval test \"x\$"$1"\" = x"_AS_ESCAPE([$2], [`], [\"$])"]],
+ [[eval test \"x\$"$1"\" = x[]AS_QUOTE_D([$2])]],
[[eval \${$1:+false} :]])]),
[$3], [$4])])
m4_define([AS_VAR_SET],
[AS_LITERAL_WORD_IF([$1],
[$1=$2],
- [eval "$1=_AS_ESCAPE([$2], [`], [\"$])"])])
+ [eval "$1="AS_QUOTE_D([$2])])])
# AS_VAR_SET_IF(VARIABLE, IF-TRUE, IF-FALSE)