Escape backquote in a double-quoted string in shell - shell

For the command: /usr/bin/sh -c "ls 1`" (a backquote after 1).
How to make it run successfully? Adding a backslash before "`" does not work.
` is a special char as we know, and I tried surrounding it with single quote too (/usr/bin/sh -c "ls 1'`'"), but that doesn't work either.
The error always are:
% /usr/bin/sh -c "ls 1\`"
Unmatched `

You need to escape the backtick, but also escape the backslash:
$ touch 1\`
$ /bin/sh -c "ls 1\\\`"
1`
The reason you have to escape it "twice" is because you're entering this command in an environment (such as a shell script) that interprets the double-quoted string once. It then gets interpreted again by the subshell.
You could also avoid the double-quotes, and thus avoid the first interpretation:
$ /bin/sh -c 'ls 1\`'
1`
Another way is to store the filename in a variable, and use that value:
$ export F='1`'
$ printenv F
1`
$ /bin/sh -c 'ls $F' # note that /bin/sh interprets $F, not my current shell
1`
And finally, what you tried will work on some shells (I'm using bash, as for the above examples), just apparently not with your shell:
$ /bin/sh -c "ls 1'\`'"
1`
$ csh # enter csh, the next line is executed in that environment
% /bin/sh -c "ls 1'\`'"
Unmatched `.
I strongly suggest you avoid such filenames in the first place.

Use single quotes instead:
/usr/bin/sh -c 'ls 1\`'

/usr/bin/sh -c "ls '1\`'"

Related

Why getting blank output when running "/bin/sh" with "-c" option [duplicate]

This question already has answers here:
How to use positional parameters with "bash -c" command?
(2 answers)
Closed 14 days ago.
I am running /bin/sh with -c option as below but getting a blank output:
[root#dockerhost dproj]# /bin/sh -c echo helloworld
[root#dockerhost dproj]#
Why the above command is not printing helloworld?
I have tried to read the man page but not able to understand anything.
From man sh:
-c Read commands from the command_string operand.
Set the value of special parameter 0
(see Section 2.5.2, Special Parameters) from the
value of the command_name operand and the positional
parameters ($1, $2, and so on) in sequence from the
remaining argument operands.
No commands shall be read from the standard input.
/bin/sh -c echo helloworld runs the command echo, which prints a blank line. You meant to type:
/bin/sh -c "echo helloworld"
which runs the command echo helloworld.
The -c option to sh causes the first non-option argument to be treated as a string of commands to run in a new shell. The remaining arguments are used to fill in the that shell's numbered arguments, starting with $0 (the "name" under which the shell process is running.) These arguments are not automatically parsed to any utility executed by that shell.
So if you invoke sh with /bin/sh -c echo helloworld:
the input passed to the shell interpreter sh is simply echo
helloworld becomes $0 in the sh session.
Normally, $0 in a shell invocation should be the name of the shell; that's what it will be set to by default:
bash$ /bin/sh
$ echo $0
/bin/sh
$ exit
bash$ echo $0
bash
Since the command given to the shell by -c is interpreted as though it were input to the shell itself, you can use $n in the command in order to refer to the extra arguments to the shell interpreter. If you want to do that, you need to remember to single-quote the -c option argument so that it's contents are not interpreted by the outer shell. Perhaps studying the following will help:
bash$ /bin/sh -c 'echo $0' fakename # Correctly single-quoted
fakename
bash$ /bin/sh -c "echo $0" fakename # Here, $0 is expanded by the outer shell
bash
bash$ /bin/sh -c 'echo "$#"' sh arg1 arg2 arg3 # $0 is not part of $#
arg1 arg2 arg3

Using /bin/bash -l -c with concatenated commands does not pass environment variables

I'm trying to execute a series of bash commands using /bin/bash -l -c as follows:
/bin/bash -l -c "cmd1 && cmd2 && cmd3..."
What I notice is that if cmd1 happens to export an environment variable, it is not seen by cmd2 and so on. If I run the same concatenated commands without the /bin/bash -l -c option, it just runs fine.
For example, this does not print the value of MYVAR:
$/bin/bash -l -c "export MYVAR="myvar" && echo $MYVAR"
whereas, the following works as expected
$export MYVAR="myvar" && echo $MYVAR
myvar
Can't find why using /bin/bash -l -c won't work here? Or are there any suggestion how to make this work with using /bin/bash -l -c?
variables are exported for child processes, parent process environment can't be changed by child process so consecutive commands (forking sub processes can't see variables exported from preceeding command), some commands are particular becuase don't fork new process but are run inside current shell process they are builtin commands only builtin commands can change current process environment export is a builtin, for more information about builtins man bash /^shell builtin.
Otherwise expansion is done during command parsing:
/bin/bash -l -c "export MYVAR="myvar" && echo $MYVAR"
is one command (note the nested quotes are not doing what you expect because are closing and opening quoted string, myvar is not quoted and may be split). So $MYVAR is expanded to current value before assigned to myvar.
export MYVAR="myvar" && echo $MYVAR
are two commands and $MYVAR because && is bash syntax (not literal like "&&"), is expanded after being assigned.
so that $ have a literal meaning (not expanded) use single quotes or escape with backslash.
/bin/bash -l -c 'export MYVAR="myvar" && echo "$MYVAR"'
or
/bin/bash -l -c "export MYVAR=\"myvar\" && echo \"\$MYVAR\""

How to pass arguments with spaces to MinGW-MSYS shell scripts from the Windows command line?

I have a sh script, myscript.sh, which takes a directory as an input argument and does some recursive processing of the files in that directory. I want to run this script in Windows command line (I use the MinGW/MSYS distribution).
How do I properly provide a path with spaces as an input argument?
For example, I want to give a path, 'dirA\dir B'. I tried many different combinations, including
sh -c 'myscript.sh "dirA/dir B"'
sh -c 'myscript.sh "dirA/dir\ B"'
sh -c "myscript.sh 'dirA/dir\\ B'"
sh -c "myscript.sh \"dirA/dir B\" "
sh -c "myscript.sh dirA/dir\ B "
But on all of them the script understands the path as 'dirA/dir'.
cmd doesn't understand single quotes. If sh does, then make the outer quotes double, so that you're double-quoting the argument at the cmd prompt, and passing the single quotes to sh to interpret.
The third option you listed is the right idea, but it's not clear to me what you're doing with the \\. Does sh require you to escape the space within a double-quoted string? If I'm not mistaken, it's either/or, not both. One of these should work (depending on whether your sh Windows port uses Unix-style forward slash path separators or Windows-style backslash separators or both):
sh -c "myscript.sh 'dirA/dir B'"
or
sh -c "myscript.sh 'dirA\dir B'"
I'm not sure why the fourth option doesn't work. That method works fine for passing double-quoted arguments to PowerShell from cmd. For example, this works:
powershell -noexit "sl \"C:\Program Files\""
This leads me to suspect that it's sh that's having a problem with the path arguments 'dirA/dir B' and "dirA/dir B" -- especially if the suggestions above don't work.
Speaking of PowerShell, you might want to give that a try instead. Any of the following should work:
sh -c 'myscript.sh "dirA/dir B"'
sh -c "myscript.sh 'dirA/dir B'"
sh -c 'myscript.sh ''dirA/dir B'''
sh -c "myscript.sh `"dirA/dir B`""

Why is this bash variable empty when I just set it?

$ sudo sh -c "FOO=bar echo Result:${FOO}"
Result:
Why is the value stored in FOO not displayed?
Because bash replaces ${FOO} before calling sudo. So what sh -c actually sees is:
FOO=bar echo Result:
Besides, even if you tried
FOO=bar echo Result:${FOO}
It still won't work1. To get that right, you can do:
FOO=bar; echo Result:${FOO}
Now that that is fixed, let's get back to sh -c. To prevent bash from interpreting the string you are giving to sh -c, simply put it in ' instead of ":
sudo sh -c 'FOO=bar; echo Result:${FOO}'
1 Refer to the comments for reason.
This doesn't work, because the variable FOO is set for the following command, echo, but the ${FOO} is replaced by the enclosing shell.
If you want it to work, you must set the variable FOO for the enclosing shell and wrap the echo ... in single quotes
sudo FOO=bar sh -c 'echo Result:${FOO}'

Inline bash script variables

Admittedly, I'm a bash neophyte. I always want to reach for Python for my shell scripting purposes. However, I'm trying to push myself to learn some bash. I'm curious why the following code doesn't work.
sh -c "F=\"123\"; echo $F"
It doesn't work because variable expansion in the double-quoted string happens before the command is called. That is, if I type:
echo "$HOME"
The shell transforms this into:
echo "/home/lars"
Before actually calling the echo command. Similarly, if you type:
sh -c "F=\"123\"; echo $F"
This gets transformed into:
sh -c "F=\"123\"; echo"
Before calling a the sh command. You can use single quotes to inhibit variable expansion, for example:
sh -c 'F="123"; echo $F'
You can also escape the $ with a backslash:
sh -c "F=\"123\"; echo \$F"
Not an answer to the core question, but if anyone is looking to do this inline in a (subjectively) more elegant way than bash -c:
( export MY_FLAG="Hello"; echo "$MY_FLAG" )
The syntax is nicer, no escape chars etc.

Resources