How to raise and take care of shell scripts: How to quote
When should I quote, when not?
A good quoting principle is: Better too much than too little. But you have to take care on HOW to quote. Quoting prevents you from errors caused by blanks, tabs and other nasty parts of values. Unfortunally there are some situations, when quoting is not applicable or unnecessary. I don't want to explain all quoting rules here - this is done in many tutorials, books or even in the shell manuals. I want to show some examples, which use a kind of special way to work with quotes, and to describe some rules how to protect quotes against themselfes.
In some cases quotes are unnecessary - for instance if you use variables, from whom you know there are defined and numeric:
- the exit code of the last executed command $?.
- the output of a wc call
- the number of arguments within a shell script $#
- and so on
Other situations may request to use no quotings. The next example uses the leak of quotes to fill an array:
line="field1 field2 field3 field4"
declare -a fld_arr
fld_arr=($line) # do not quote here to be able to access every single field
for (( i=0; i < ${#fld_arr[*]}; i++ )); do
echo $i ${fld_arr[$i]}
done
The ' quoting sign (single quote) allows you to prevent every character in your text from being interpreted by the shell. But what if your text contains this character? It can't be escaped because no escape characters are evaluated within the single quotes. It would be the end of your quoted text every time. In the next samples I use a modified version of some of my replies in a web forum's thread:
# single quote embedded in a text
jan@jack:~/tmp> echo '123'(456\('
bash: syntax error near unexpected token `('
# try to escape the single quote
jan@jack:~/tmp> echo '123\'(456\('
bash: syntax error near unexpected token `('
# so it works: the single quote is placed outside the quoted text
# and is hided from being interpreted using double quotes
jan@jack:~/tmp> echo '123'"'"'(456\('
123'(456\(
How one can use this method to deal with variable, unknown content? I created a directory containing some very nasty file names - in my opinion mp3 files often have such unbelievable names, some mp3 administration programs don't stop at nothing ;-)
jan@jack:~/tmp/trash_names> ls 123'(456\(b l a) 123'(456\(bla) 123 "buh" 456 jan@jack:~/tmp/trash_names> find . -type f -printf "ls |%p|\n" | > sed "s/'/'\"'\"'/g;s/|/'/g" | sh ./123'(456\(bla) ./123'(456\(b l a) ./123 "buh" 456
If you forward these filenames simply using a ls to a loop to work with variable values (to
rename the files, for instance) you'll get the problems I mentioned above. To work around it you can hide the outer
single quotes using another character. The -printf option unfortunally is only available in
the GNU version of the find command. I use the pipe character as replacement - of course this character may not be found
within the file names.
Next the file names go through a sed laundry. Now ervery single quote will be replaced by the
character sequence '"'"', at last the escape pipe sign we used in the first step is re-replaced
by the single quote - now we can use the file names.
Sometimes other quote characters must be hided too - for instance when working with encapsulated commands: How to deal with commands, which use the output of other commands using some other command's output (onion-skin commands ;-)? The sample you see below calculates the next working day in the format "Monday, 21. of July" and displays it. The construct intensively uses the options available in the GNU version of the date command.
date +"%A, %d. of %B" -d "today +`if test \`date +%w\` -gt 4; then \
expr 8 - \`date +%w\`; else echo 1; fi` days"
Here's a step-by-step description of the monster command parts (you should've opened the date manual in another terminal):
- Our goal is to call the date using this format:
date +"%A, %d. of %B" -d "today +X days"
where X depends on the current weekday. On friday we must add 3 days to get next monday, on saturday 2 days are to be added and for all other weekdays the next day is the next working day. - We get the current weekday using date +%w, it'll return values from 0 (sunday) up to 6 (saturday). Knowing this value we can use control structures (weekday > 4).
- To do this we use the if-then-else shell control:
if test `date +%w` -gt 4; then ...; else echo 1; fi
Within the branches we return the calculated number of days, the control structure output is used as the argument of the -d date's option. That's why we must escape all backslashes within the control structure, because they otherwise would stop the shell's command substitution:
`if test \`date +%w\` -gt 4; then ...; else echo 1; fi` - For friday and saturday we calculate the number of days to add to the current day. To get the right value we must
subtract the weekday from 8. For example this way:
expr 8 - `date +%w`
This calculation is embedded in the control structure - that means:
`if test \`date +%w\` -gt 4; then expr 8 - \`date +%w\`; else echo 1; fi`
O.K. - we got the result. But now we want to store this value in a variable. For this we pull a new "`...`" skin on the command. Which characters we have to escape from being interpreted now? It's quite simple: All characters having a special meaning to the shell, in our case: quotes, backslash, backtick. We prepend every such character with an escape character, namly the backslash - and we're ready:
NEXT_WDAY="`date +\"%A, %d. of %B\" -d \"today +\`if test \\\`date +%w\\\` -gt 4; then \\
expr 8 - \\\`date +%w\\\`; else echo 1; fi\` days\"`"

