Confusing Shell Quotation - bash

I'm not a newbie to shell, but still got confused with some not so complex quotation problems. I guess there must be something misunderstood.
a: echo 'Don\'t quote me // Don quote me
b: echo Don'\t' quote me // Don quote me
c: echo Don\t quote me // Dont quote me
d: echo Don"\t" qoute me // Don quote me
Above three quotations go quite against my intuition. Doesn't single quote '...' literally returns what is quoted? What I thought is..
For a: in single quoted 'Don\', \ is nothing but a common character. So a) should be Don\t quote me.
For b: like a), '\t' suppressed the special meaning of \t, so I thought b) should be Don\t quote me too.
For c: I understand why c works, but don't understand the diff between a&b and c.
For d: no difference between ' and "?
Probably I misunderstand how shell parse and execute the line of command..
Problem solved by using /bin/echo instead of (built-in)echo on Mac. Latter one will interpret backslash.

As per bash
the first one should return Don\t quote me
the second should return like the first one
the third should return Dont quote me
the last one should return Don\t qoute me
Why:
first one you scaped the don\t by putting it inside single quotes
you scaped only the \t
there is no scaping because \t means print the character after \ as is
double quote doesnt scape scape characters

Your understanding of shell quoting is correct, but it appears that echo on OSX is a shell builtin which interprets backslash escapes. This behavior can be turned off by executing shopt -u xpg_echo.
See here for more information:
How can I escape shell arguments in AppleScript?

Related

Behavior of echo with many backslashes

I have a project with the goal of implementing the same behavior as the echo command. My problem is with backslashes. My information says that when a backslash appears you must considering the next character as a simple character, but here I guess it's not the same.
This an example :
echo \\\\
OUTPUT : \
The problem here is that I expect that the output to be 2 backslashes, not just one.
To get 2 backslashes I need to write 6 backslashes:
echo \\\\\\
Can anyone help me to understand this behavior?
There are multiple layers where the backslashes are interpreted. It is an escape character in the shell(among other places). A backslash followed by a character is an escape code for another character(for instance, \n is interpreted as a line break).
When you first execute echo \\\\\\, the shell parses the escape sequences and ends up passing \\\ to the command(in this case echo).
Quoting the string on the shell will prevent interpretation there(i.e. echo "\\" will literally pass two backslashes to the echo command). You also either have an additional layer of interpretation or your program is incorrectly handling the backslash sequence. Ultimately, you'll need to escape it for each layer.

b2 command finds ICU when called directly but not from indirect bash script variable

I have this strange issue with my bash script. I compile boost as part of it. The call from the script looks like this:
./b2 --reconfigure ${PARALLEL} link=static cxxflags=-fPIC install boost.locale.iconv=off boost.locale.posix=off -sICU_PATH="${ICU_PREFIX}" -sICU_LINK="${BOOST_ICU_LIBS}" >> "${BOOST_LOG}" 2>&1
That command works perfectly well. The log file shows that it finds ICU without a problem. However, if I change it to run from a variable, it no longer finds ICU (but it still compiles everything else):
bcmd="./b2 --reconfigure ${PARALLEL} link=static cxxflags=-fPIC install boost.locale.iconv=off boost.locale.posix=off -sICU_PATH=\"${ICU_PREFIX}\" -sICU_LINK=\"${BOOST_ICU_LIBS}\""
$bcmd >> "${BOOST_LOG}" 2>&1
What's the difference? I would like to be able to use the second approach so that I can pass the command into another function before running it.
Don't use a variable to store complex commands involving quotes that are nested. The problem is when you call the variable with just $cmd, the quotes are stripped incorrectly. Putting commands (or parts of commands) into variables and then getting them back out intact is complicated.
Quote removal is part of the one of the word expansions done by the shell. From the excerpt seen in POSIX specification of shell
2.6.7 Quote Removal
The quote characters ( backslash, single-quote, and double-quote) that were present in the original word shall be removed unless they have themselves been quoted.
Your example can be simply reproduced by a simple example. Assuming you have a few command flags (not actual ones)
cmdFlags='--archive --exclude="foo bar.txt"'
If you carefully look through the above, it contains 2 args, one --archive and another for --exclude="foo bar.txt", notice the double-quotes which needs to be preserved when you are passing it.
Notice how the quotes are incorrectly split when I don't quote cmdFlags, in the printf() call below
printf "'%s' " $cmdFlags; printf '\n'
'--archive' '--exclude="foo' 'bar.txt"'
and compare the result with one with proper quoting done below.
printf "'%s' " "$cmdFlags"; printf '\n'
'--archive --exclude="foo bar.txt"'
So along with the suggestion of properly quoting the variable, the general suggestion would be to use an array to store the flags and pass the quoted array expansion
cmdArray=()
cmdArray=(./b2 --reconfigure ${PARALLEL} link=static cxxflags=-fPIC install boost.locale.iconv=off boost.locale.posix=off -sICU_PATH="${ICU_PREFIX}" -sICU_LINK="${BOOST_ICU_LIBS}")
and pass the array as
"${cmdArrray[#]}" >> "${BOOST_LOG}" 2>&1
Try to use eval when you want to execute a string as a command. This way, you won't have issues regarding strings that have spaces etc. The expanded cmd string is not re-evaluated by bash hence, things like "hi there" are expanded as two separate tokens.
eval "$bcmd" >> "${BOOST_LOG}" 2>&1
To demonstrate this behavior, consider this code:
cmd='echo "hi there"'
$cmd
eval "$cmd"
Which outputs to:
"hi there"
hi there
The token "hi there" is not re-evaluated as a quoted string.
Use single quota instead of two.
bcmd='./b2 --reconfigure ${PARALLEL} link=static cxxflags=-fPIC install boost.locale.iconv=off boost.locale.posix=off -sICU_PATH=\"${ICU_PREFIX}\" -sICU_LINK=\"${BOOST_ICU_LIBS}\"'
Bash documentation states that single quota does not interpolate:
3.1.2.2 Single Quotes
Enclosing characters in single quotes (') preserves the literal value of each character within the quotes. A single quote may not occur between single quotes, even when preceded by a backslash.
That way you omit double-quota stripping and removing problem, and your string will be passed correctly. If you want to stay with double quotes, you have to vary that they DO NOT preserve literal value of certain characters: $, ' and \ unless preceded with \, as manual states:
3.1.2.3 Double Quotes
Enclosing characters in double quotes (") preserves the literal value of all characters within the quotes, with the exception of $, `, \, and, when history expansion is enabled, !. The characters $ and ` retain their special meaning within double quotes (see Shell Expansions). The backslash retains its special meaning only when followed by one of the following characters: $, `, ", \, or newline. Within double quotes, backslashes that are followed by one of these characters are removed. Backslashes preceding characters without a special meaning are left unmodified. A double quote may be quoted within double quotes by preceding it with a backslash.
In your example you forgot to mark $ with backslash as well. Difference between these two is explained perfectly by Adam here: Differences between single and double quota

Is this some kind of regex within bash variable instantiation?

What do those two assignations (i and C omitting the first one to void) do? Is it some kind of regex for the variable? I tried with bash, but so far there were no changes in the output of my strings after instantiating them with "${i//\\/\\\\}" or "\"${i//\"/\\\"}\""
C=''
for i in "$#"; do
i="${i//\\/\\\\}"
C="$C \"${i//\"/\\\"}\""
done
${i//\\/\\\\} is a slightly complicated-looking parameter expansion:
It expands the variable $i in the following way:
${i//find/replace} means replace all instances of "find" with "replace". In this case, the thing to find is \, which itself needs escaping with another \.
The replacement is two \, which each need escaping.
For example:
$ i='a\b\c'
$ echo "${i//\\/\\\\}"
a\\b\\c
The next line performs another parameter expansion:
find " (which needs to be escaped, since it is inside a double-quoted string)
replace with \" (both the double quote and the backslash need to be escaped).
It looks like the intention of the loop is to build a string C, attempting to safely quote/escape the arguments passed to the script. This type of approach is generally error-prone, and it would probably be better to work with the input array directly. For example, the arguments passed to the script can be safely passed to another command like:
cmd "$#" # does "the right thing" (quotes each argument correctly)
if you really need to escape the backslashes, you can do that too:
cmd "${#//\\/\\\\}" # replaces all \ with \\ in each argument
It's bash parameter expansions
it replace all backslashes by double backslashes :"${i//\\/\\\\}
it replace all \" by \\" : ${i//\"/\\\"}
Check http://wiki.bash-hackers.org/syntax/pe

Characters \$ in echo

I need to have in a string the text "\$CONDITIONS". I tried used:
> echo "\$CONDITIONS"
$CONDITIONS
> echo "\\$CONDITIONS"
\
Could you help me? What should I enter in echo command to get
\$CONDITIONS
as a result?
echo doesn't do anything except print exactly the string you pass in. The trick is to know enough about the shell to be able to pass in the string you want.
If you don't need the shell to perform substitutions on the value, simply use single quotes instead.
echo '\$CONDITIONS'
If you absolutely need to use double quotes, you can still single-quote individual parts of the string. Single quotes adjacent to double quotes will get pasted together into a single string before the shell passes it on.
echo '\$'"CONDITIONS"
Good old echo is slightly tired; you might also want to consider printf which is somewhat more versatile.
printf "\x5c\x24CONDITIONS\n"
(I'd normally use single quotes here as well; the double quotes are just to demonstrate that this works even with double quotes. But be careful with the backslashes; these happen to work even with single backslashes, but often they will need to be doubled if you want literal backslashes inside double quotes.)
To review what happened in your failed attempts,
echo "\$CONDITIONS" # produces $CONDITIONS
the backslash properly escapes the dollar sign from the shell, and is removed as part of the process. So you are saying, a literal dollar sign, and the text CONDITIONS.
echo "\\$CONDITIONS" # produces \
Here, the backslash similarly escapes the backslash, and the shell expands the variable $CONDITIONS which is unset or empty.
echo "\\\$CONDITIONS"
Well, this works, but it's ugly. There is a backslash-escaped backslash, and a backslash-escaped dollar sign, and the text CONDITIONS.
Backslashes and dollar signs (and backticks `) don't get processed inside single quotes, so that's what you should usually use if your string contains any of these (and more generally, if you don't specifically require the shell to handle these constructs).
Backslashes are kind of tricky inside double quotes. The shell will remove the ones it processes (so \$ gets turned into just $) but retain the ones it doesn't actually do anything with (so \x is preserved as \x inside double quotes). Without quotes, the behavior is different again. (Not even going into that rabbithole. Just use quotes.)
Use \\\ to achieve this.
#!/bin/bash
echo "\\\$CONDITION" # prints \$CONDITION

How to escape characters from a single command?

How do I escape characters in linux using the sed command?
I want to print something like this
echo hey$ya
But I'm just receiving a
hey
how can escape the $ character?
The reason you are only seing "hey" echoed is that because of the $, the shell tries to expand a variable called ya. Since no such variable exists, it expands to an empty string (basically it disappears).
You can use single quotes, they prevent variable expansion :
echo 'hey$ya'
You can also escape the character :
echo hey\$ya
Strings can also be enclosed in double quotes (e.g. echo "hey$ya"), but these do not prevent expansion, all they do is keep the whole expression as a single string instead of allowing word splitting to separate words in separate arguments for the command being executed. Using double quotes would not work in your case.
\ is the escape character. So your example would be:
~ » echo hey\$ya
hey$ya
~ »

Resources