(cross post from bug-make) A complete example of quoting an arbitrary value as a word in a shell script

Poor Yorick <[email protected]>
Newsgroups gmane.comp.gnu.make.general
Message-ID <[email protected]>
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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.