help-make
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

(cross post from bug-make) A complete example of quoting an arbitrary va


From: Poor Yorick
Subject: (cross post from bug-make) A complete example of quoting an arbitrary value as a word in a shell script
Date: Wed, 26 Oct 2022 21:57:55 +0300

Hi,

Posted this already to bug-make, but should have posted here in the first place.

Maybe this has been done before, but I couldn't find it on the nets. The example Makefile below illustrates how to quote an arbitrary value as a word in a shell script. One use case for this is to evaluate a value as a complete shell script, similar to .ONESHELL but allowing all shell features, including
heredocs.  Surely there are many use cases.


The process to quote a value is:

    Escape single quotes.

    Quote newlines.

        If $'\n' notation is available in the shell, use that.

        Otherwise, enclose newlines in single quotes.

    Enclose everything else in Single quotes.


In a Posix shell, where $'...' is not available it's tricky because newlines are stripped from the result of a command subsitution, so $(printf '\n') also does not work directly. The workaround used below is to have printf produce both the a newline and the single quotes that enclose it. For example, the following script,

    echo 'hello'
    echo goodbye

is transformed into,

    'echo '\''hello'\''
    ''echo goodbye'

and can be evaluated>

    eval 'echo '\''hello'\''
    ''echo goodbye'


Here is an example Makefile>

<code>
#SHELL = bash
SHELL = dash

define newline


endef


define script0
        echo 'line one
        line two
        '
        cat <<-'eof'
                line three
                line four
        eof

endef


ifeq ($(shell printf '%s' $$'\n'),$$\n)
    $(warning posix shell quoting)
        # this is more difficult

# Assume $(1) will be enclosed in single quotes, break each single quote # out of the enclosing quotes, and replace it with an escaped single quote.
        shquotequote = $(subst ','\'',$(1))

# Assume $(1) will be enclosed in single quotes, break each newline out of
        # the enclosing quotes, and replace it with a command substitution that
        # produces a newline enclosed in single quotes.  This results in some
# syntactically unnecessary quoting, but avoids having newline characters
        # stripped from the result of the command substitution.
shnlquote = $(subst $(newline),'$$(printf "'\n'")','$(call shquotequote,$(1))')

        # Just print the string using printf, using eval to perform the newline
        # command subsitutions and strip away the extra layer of quoting added
        # shnlquote and shquotequote.
        shquote = "$$(eval printf '%s' "$(call shnlquote,$(1))")"
else
    $(warning direct shell quoting)
        # this is easy...

        # Replace each single quote with a quoted single quote,
        # call shnlquoe to deal with newlines
        # Add the first and last single quotes
        shquote = '$(call shnlquote,$(subst ','"'"',$(1)))'

        # replace each newline character with a
        shnlquote = $(subst $(newline),'$$'\n'',$(1))
endif


script0:
        eval $(call shquote,$(script0))
.PHONY: script0

printscript0:
        printf '%s\n' $(call shquote,$(script0))
.PHONY: printscript0
</code>





The following quoting implementation also works, but was discarded in favor of
the one above:

<code>
# In a posix shell $'\n' is not a valid representation of newline, and it
# isn't possible to produce a newline using command substitution because
# newlines are stripped off the result. Instead, use printf to replace a
# newline character surrounded by double quotes.
shnlquote = $(subst $(newline),'\'$$(printf '"\n"')\'',$(1))

# Because each newline character is enclosed in an additional layer of
# quotes, enclose everything else in an additional layer of quotes too.
shquotequote = \''$(call shnlquote,$(subst ','\'\"\'\"\'',$(1)))'\'

# Finally, Use printf again to remove the extra layer of quoting, with the main
# effect here not being printf, but the quote removal that the shell
# performs as it reads the script.
shquote = "$$(printf '%s\n' $(call shquotequote,$(1)))"
</code>


Enjoy!


--
Yorick

To support this and further work, make a payment to the following Ethereum
address (Mainnet, ImmutableX Layer 2, or Loopring Layer 2 only):

        0x0b5049C148b00a216B29641ab16953b6060Ef8A6



reply via email to

[Prev in Thread] Current Thread [Next in Thread]