linux shell script: Ignore double quotes from IFS - bash

Due to the default value of IFS, I am getting following result.
STR="this is a \"valid string\""
for a in $STR; do
echo $a;
done
will give output as:
this
is
a
"valid
string"
But I don't want to split "valid string".
I need output as:
this
is
a
"valid string"
So, how can I make shell ignore double quotes from IFS?

There are two types of quotes: Literal ones, and syntactic ones. The outer quotes of STR are syntactic, which means that they are not actually part of the string, they are just used to tell Bash where the string starts and ends. The inner (escaped) quotes are literal, so they are part of the string. In other words, the literal value of your string is:
this is a "valid string"
Now if you want to loop over this, you have to remember that this string consists of five (space-separated) literal words:
this
is
a
"valid
string"
If you want to get around this, one you can either use eval (but beware of the serious security issues) or arrays:
declare -a STR=(this is a "valid string")
for str in "${STR[#]}"
do
echo "$str"
done

That's not easily possible. You could switch to Perl instead. Or, if you have full control over the string, and nobody can possibly ever insert something evil into it, you could use this code:
str="a b c \"d e\""
eval "set args $str; shift"
for i in "$#"; do
echo "$i"
done
This will output the last word without the quotes, though. And it overwrites the command lune arguments to the shell program itself.
If you need to keep the quotes, this is called parsing and should not be done by a shell program, since it becomes too complicated.

Related

Single and double quotes in the same bash input

I want to give input to a bash read function which contains both single and double quotes at the same time, then have it compare with a stored value.
This script asks a random question on startup, looking for correct syntax of some commands I want to memorize. However some of these commands contain both single and double quotes, so how to handle this with bash?
#!/bin/bash
echo "What is the secret string?"
read secret
if [ "$secret" = "123" ]
then
echo "You're Awesome!!!"
else
echo "You're memory sucks!!"
fi
So if $question is "Hi my name is 'Ed'" including the single AND double quotes in the same input, how do I work this magic?
There is no magic to be worked. The input from the user never becomes syntax within your script — it is never evaluated as code — so it can contain anything.*
* Except for the NUL character (\0) itself, since that is the string terminator character.
reading strings that contain quotes doesn't require anything special, but I'd use read -r to prevent it messing with backslashes. You can also use the -p option to supply a prompt (instead of echoing it separately):
read -r -p "What is the secret string? " secret
The only tricky thing here is specifying the string to compare with. One option is to express it as a double-quoted string, and escape the double-quote(s) within it with backslashes. You'll also need to escape any dollar signs, backticks, or backslashes the same way:
if [ "$secret" = "dq: \", sq: ', dollar: \$" ]
then
echo "You're Awesome!!!"
else
echo "Your memory sucks!!"
fi
Here's an example run of the above code:
$ ./secrettest.sh
What is the secret string? dq: ", sq: ', dollar: $
You're Awesome!!!
My question was about specifying the string in the code as Gordon mentioned, not reading it from the input. I had escaped the double quotes and single quotes in my original which is why it didnt work.
So this
if [ "$secret" = "command \'with \"options\"\'" ]
Should have been this
if [ "$secret" = "command 'with \"options\"'" ]
Thanks guys.

I want to print $ in shell, but it is always deleted

In a Linux shell, I want to print:
$300
$400
But when I do echo -e "$300\n$400" it shows:
00
00
When I do printf "$300\n$400" it shows the same thing!
So why does shell delete my dollar sign and the number right after it? Is there a way to print what I want?
You need to escape dollar $, since you are using double quotes, This will ensure the word is not interpreted by the shell.
$ echo -e "\$300\n\$400"
$300
$400
You may be aware how to access variables,
Example :
$ test="foo"
$ echo "$test"
foo
Suppose if you want to print $test, then you have use either
$ echo "\$test"
$test
OR with single quotes
$ echo '$test'
$test
In the shell, the $ character has a special meaning. It means "replace the $ and the following word or digit or special character with the value of a variable of that name". For example:
currency='EUR'
echo "The currency is $currency"
The variables 0, 1, 2, etc. contain the command line arguments to the program. So if you run your program as my-program Hello, world, you can write this code:
echo "argument 1 is $1"
echo "argument 2 is $2"
echo "both together are $1 $2, and all arguments are $*"
To make the $ character lose this special meaning, it must be written as \$. For example:
price=123
echo "The price is $price\$"
The first $ refers to the variable, and the second $ is escaped.
Alternatively you can surround your string in 'single quotes', which removes the special meaning of all characters.
To learn more about this topic, run the man bash command and read the section about variable expansion.
$ has special meaning to the shell; when it sees a $, it expects an existing shell variable name to follow. For example, $PATH.
In your case, you don't want the shell to think that you're trying to print out the value of shell variables, so you must tell the shell that the $ is indeed what you want to be displayed. This is done by preceding it with a backslash as explained in other answers.
Adding a backslash before characters is called escaping them (yes, not the most obvious terminology), and you are already using some escape characters unknowingly. (\n)
This applies to display other operators too, such as =, :, etc. Hope that helps.
You can use single quote. Enclosing characters in single-quotes (') shall preserve the literal value of each character within the single-quotes, where as enclosing characters in double-quotes(") shall preserve the literal value of all characters within the double-quotes, with the exception of the characters back quote, dollar-sign, and backslash.
echo -e '$'300"\n"'$'400

Get keys of an array with variable name in bash

In my bash script I have two arrays. Depending on some logic either one or another shall be used, so I'm getting the name of a required array in a variable varName. I can surely get the values of this array with the code below but is there a way to get it's keys? Tried several options but no luck.
declare -A foo=([a]=b [c]=d)
declare -A bar=([e]=f [g]=h)
varName=foo
varArray=$varName[#]
echo ${!varArray}
Thanks.
Not without resorting to eval, unfortunately. To be safe, make sure varName is just a single valid identifier.
[[ varName =~ ^[a-zA-Z_][a-zA-Z_0-9]+$ ]] && eval "echo \${!$varName[#]}"
eval is necessary to provide a second round of parsing and evaluation. In the first round, the shell performs the usual parameter expansion, resulting in the string echo ${!foo[#]} being passed as the single argument to eval. (In particular, the first dollar sign was escaped and so is passed literally; $varName is expanded to foo; and the quotes are removed as part of quote removal. eval then parses that string and evaluates it.
$ eval "echo \${!$varName[#]}"
# echo ${!foo [#]}
# The above is the argument that `eval` sees, after the shell
# does the normal evaluation before calling `eval`. Parameter
# expansion replaces $varName with foo and quote removal gets
# rid of the backslash before `$` and the double quotes.
a c
If you are using bash 4.3 or later, you can use a nameref.
declare -n varName=foo
for key in "${!varName[#]}"; do
echo "$key"
done
There is an IFS-safe method to get the keys (or values) of an array indirectly:
declare -a things=("an apple" "a banana")
declare -a tmp
arrayName=things
eval "tmp=(\"\${$arrayName[#]}\")"
# "${tmp[#]}" is now ("an apple" "a banana")

bash variable with hash value assigned

I have a variable that has a hash value assigned to it. For example :
hash=$1$qGqTE/jV$syM.7qpaKlCTsBXOYu2op/
now when I do echo $hash in bash it returns:
/jV.7qpaKlCTsBXOYu2op/
How do I have hash value not escape any characters? or have echo $hash return the entire string $1$qGqTE/jV$syM.7qpaKlCTsBXOYu2op/?
Any help is highly appreciated.
The definition needs single quotes:
$ hash='$1$qGqTE/jV$syM.7qpaKlCTsBXOYu2op/'
$ echo "$hash"
$1$qGqTE/jV$syM.7qpaKlCTsBXOYu2op/
Without the single quotes, the shell performs variable substitution and the result depends on the value returned by $1, $qGqTE and $syM when the definition statement is executed.
I also added double-quotes to the echo statement. This stops the shell from performing word splitting and pathname expansion. While it may be unlikely that a hash value would be affected by these, it is safer to use the double quotes. As an example of the potential problem:
$ hash='/bin/le*'
$ echo $hash
/bin/less /bin/lessecho /bin/lessfile /bin/lesskey /bin/lesspipe
$ echo "$hash"
/bin/le*
As you can see, in this case, echo $hash performs pathname expansion and returns a list of files. echo "$hash", however, works as desired. To avoid surprises such as this, it is best to put references to shell variables in double quotes.

Which form is preferable to use in Bash?

I'm studyng Bash, and I see that the form
C=example
echo "$C"
give the same result of the form
C="example"
echo $C
I'd like to know if is better put the " " in the assignment of the variable or after the $. Or if it is indifferent. or if one is consider "more beautiful" than the other.
If you're certain that a variable's value is a single word (no white space) then it's OK to use $varname or ${varname}. If you can't guarantee this, then you should use "$varname" or "${varname}". Note that bash does word-splitting before interpreting your command, so you may actually get a syntax error if you don't quote the expression, for example
C="white space"
if [ -z $C ]
then
...
fi
will result in syntax error:
-bash: [: white: binary operator expected
while this works fine:
C="white space"
if [ -z "$C" ]
then
...
fi
This is due to the fact after variable expansion in the first, unquoted case bash sees this:
if [ -z white space ]
then
...
fi
and the -z operator expects just one, not two arguments. In the second, quoted case bash sees this:
if [ -z "white space" ]
then
...
fi
i.e. just a single argument as required. Note also that quotes were used in assignment
C="white space"
as it would also produce an error if you wrote
C=white space
since this would mean: execute command space with environment containing an added variable C=white.
So, in general you should quote these expressions to ensure your code is more robust against unforeseen variable values. This is especially true if the variable value comes from input, file etc. It is usually safe to drop the quotes for integer variables or when you just want to display the value of a variable as in echo $C.
It matters when the string contains whitespace characters. Without the quotes, whitespace characters are treated as token delimiters and bash tries to interpret the substituted string as an expression.
Always put quotes to be safe, when you don't intend to evaluate the variable as a part of the expression.
Imagine you change the input from "example" to "two words", then you could encounter strange behaviour or even syntax errors when executing the script, in case you have overlooked the above.
In other words,
C="abc def"
# the echo command receives one argument: "abc def"
echo "$C"
# echo receives two arguments: "abc" and "def"
echo $C
# bash tries to execute the program "abc" with a first argument "def"
$C
# bash tries to execute the program "abc def"
"$C"
A good documentation about quotes and word-spliting :
"USE MORE QUOTES!" They are vital. Also, learn the difference between ' and " and `. See http://mywiki.wooledge.org/Quotes and http://wiki.bash-hackers.org/syntax/words
greybot sample from IRC freenode #bash is talking to the world =)
If it's a one-word constant, it's irrelevant.
However, you should read about the two kinds of quoting. Try this article and this documentation. There is also a SO question.
Try with a real example with whitespace. For the string example you do not need any quoting at all. So create a file called This is an example.txt and then retry. Substitute echo with ls...

Resources