Magic Aliases: A Layering Loophole in the Bourne Shell (2003, by Simon Tatham)
(Simon Tatham is the author of PuTTY!)
mquote [-dnpQv] ...
-d Use "declare -p" for quoting.
-n No run. Only print the command after quoting without executing it.
This is implicit if there is no ``..`` in the argument.
-p Use "printf %q" for quoting.
-Q Use "${var@Q}" for quoting. This requires Bash 4.4 .
-v Verbose. Print the command before execution.
- Bash
3.2
is required. - The
mquote
alias only works in interactive Bash shell and command history must be enabled (set -o history
). - In the following examples which use
ssh
, we assume the remote shell is also Bash.
Say we have this demo command:
$ echo '111"222' | awk -F\" '{ print $1 $2 }'
333
How should the command be quoted if we want to define it as an alias? Just pass the whole command literally to mquote
:
$ mquote echo '111"222' | awk -F\" '{ print $1 $2 }'
>>> echo" '111\"222' | awk -F\\\" '{ print \$1 \$2 }'"
Then, copy-n-paste the output to the alias
command:
$ alias foo=echo" '111\"222' | awk -F\\\" '{ print \$1 \$2 }'"
$ foo
333
Or you can copy-n-paste the mquote
output to a var assignment:
$ cmd=echo" '111\"222' | awk -F\\\" '{ print \$1 \$2 }'"
$ eval "$cmd"
333
$ ssh 127.0.0.1 "$cmd"
333
Still using this example:
$ echo '111"222' | awk -F\" '{ print $1 $2 }'
333
To run it over ssh, we can ask mquote
to automatically quote the command by using ``..``
:
$ mquote ssh 127.0.0.1 ``echo '111"222' | awk -F\" '{ print $1 $2 }'``
333
That's to say, mquote
will automtically quote the part between ``..``
on the fly.
With -v
(verbose) we can see the real command after quoting:
$ mquote -v ssh 127.0.0.1 ``echo '111"222' | awk -F\" '{ print $1 $2 }'``
>>> ssh 127.0.0.1 echo\ \'111\"222\'' | awk -F\" '\''{ print $1 $2 }'\'
333
With -n
(no run) it'll only print the command without executing it:
$ mquote -n ssh 127.0.0.1 ``echo '111"222' | awk -F\" '{ print $1 $2 }'``
>>> ssh 127.0.0.1 echo" '111\"222' | awk -F\\\" '{ print \$1 \$2 }'"
$ mquote -n -d ssh 127.0.0.1 ``echo '111"222' | awk -F\" '{ print $1 $2 }'``
>>> ssh 127.0.0.1 "echo '111\"222' | awk -F\\\" '{ print \$1 \$2 }'"
$ mquote -n -p ssh 127.0.0.1 ``echo '111"222' | awk -F\" '{ print $1 $2 }'``
>>> ssh 127.0.0.1 echo\ \'111\"222\'\ \|\ awk\ -F\\\"\ \'\{\ print\ \$1\ \ \$2\ \}\'
$ mquote -n -Q ssh 127.0.0.1 ``echo '111"222' | awk -F\" '{ print $1 $2 }'``
>>> ssh 127.0.0.1 'echo '\''111"222'\'' | awk -F\" '\''{ print $1 $2 }'\'''
$ echo '111"222' | awk -F\" '{ print $1 $2 }'
333
To run the above command with 2 level nested ssh (ssh host1 ssh host2 ...
), we can use the ``{2}..``
syntax:
$ mquote -v ssh 127.0.0.1 ssh 127.0.0.1 ``{2}echo '111"222' | awk -F\" '{ print $1 $2 }'``
>>> ssh 127.0.0.1 ssh 127.0.0.1 echo'" '\'111'\"222'\'' | awk -F\\\" '\''{ print \$1 \$2 }'\'\"
333
For more levels quoting, use ``{3}..``
, ``{4}..``
, ...
$ mquote ssh 127.0.0.1 ssh 127.0.0.1 ssh 127.0.0.1 ``{3}echo '111"222' | awk -F\" '{ print $1 $2 }'``
333
And ``{1}..``
is the same as ``..``
.
Say we want to write an echo
command which outputs " ' # $( < > \ | )
. Just enclose it with ``..``
:
$ mquote -v echo ``" ' # $( < > \ | )``
>>> echo '" '\'' # $( < > \ | )'
" ' # $( < > \ | )
Over ssh:
$ mquote ssh 127.0.0.1 echo ``{2}" ' # $( < > \ | )``
" ' # $( < > \ | )
$ mquote ssh 127.0.0.1 ssh 127.0.0.1 echo ``{3}" ' # $( < > \ | )``
" ' # $( < > \ | )
$ mquote echo foo ``$( < > )`` bar ``" ' \ |``
foo $( < > ) bar " ' \ |
$ mquote ssh 127.0.0.1 echo foo ``{2}$( < > )`` bar ``{2}" ' \ |``
foo $( < > ) bar " ' \ |
People often write sed/awk/perl/...
one-liners and it's error-prone when quoting the
script part (usually -e ...
or -c ...
) in shell. With mquote
, you can focus on
the util/language's script itself without worrying about shell quoting.
$ mquote echo | sed -e ``s/.*/"'/``
"'
$ mquote awk ``BEGIN { print "\"" "'" }`` /dev/null
"'
$ mquote sh -c ``echo "\"'"``
"'
$ mquote python3 -c ``print('"\'')``
"'
$ mquote perl -e ``print '"', "'", "\n"``
"'
Quite often I need to turn on set -x
to debug something:
$ set -x
$ do something
$ set x
# or
$ set -x; do something; set x
With set-x
you can just set-x do something
. For example:
$ sum=0; for ((i=1; i<=5; i)); do ((sum = i)); done; echo $sum
15
$ set-x sum=0; for ((i=1; i<=5; i)); do ((sum = i)); done; echo $sum
eval 'sum=0; for ((i=1; i<=5; i)); do ((sum = i)); done; echo $sum'
sum=0
(( i=1 ))
(( i<=5 ))
(( sum = i ))
(( i ))
(( i<=5 ))
(( sum = i ))
(( i ))
(( i<=5 ))
(( sum = i ))
(( i ))
(( i<=5 ))
(( sum = i ))
(( i ))
(( i<=5 ))
(( sum = i ))
(( i ))
(( i<=5 ))
echo 15
15
set 0
set x