Common Bash programming errors (end)

Original author: Greg Wooledge
  • Transfer
The end of the translation of Bash Pitfalls . The previous parts are available on the Shells blog ( part 1 ... part 2 ) and on my blog ...

22. echo "Hello World!"

The problem is that in the Bash interactive shell this command will throw an error:
 bash:! ": event not found

This is because, at default settings, Bash performs csh-style command history substitution using an exclamation mark. There is no such problem in scripts, only in an interactive shell.

The obvious solution does not work here:
 $ echo "hi \!"  
hi \!

You can enclose this string in single quotes:
 echo 'Hello World!'

But the most suitable solution here is to temporarily turn off the parameter histexpand . This can be done with the command set +H ... set +o histexpand ...
 set + H  
echo "Hello World!"

Why then not always use single quotes? Imagine that you want information about mp3 files:
 mp3info -t "Don't Let It Show" ...  
mp3info -t "Ah! Leah!"  ...

Single quotes are not suitable here, because song names contain apostrophes in the names, and the use of double quotes will lead to a problem with the substitution of the history of commands (and if double quotes were also contained in the file names, it would have turned out to be a dash that). Since I personally (Greg Wooledge, author of the text) never use command history substitution, I just put the command set +H in my .bashrc. But this is a matter of habit and everyone decides for himself.

23. for arg in $ *

In Bash, just like in other shells of the Bourne shell family, there is a special syntax for working with positional parameters in turn, but $* also $@ not quite what you need: after parameter substitution, they become a list of words passed in arguments, rather than a list of options individually.

Here is the correct syntax:
 for arg in "$ @"

Or simply:
 for arg

for arg matches for arg in "$@" . A variable enclosed in double quotes "$@" is a special street magic, thanks to which every command line argument is enclosed in double quotes, so that it looks like a separate word. In other words, "$@" converted to a list "$1" "$2" "$3" , etc. This trick will work in most cases.

Consider an example:
# wrong  
for x in $ *;  do echo "parameter: '$x'" done

This code will print:
$ .§myscript 'arg 1' arg2 arg3  
parameter: 'arg'  
parameter: '1' parameter: 'arg2' parameter: 'arg3'

Here's what it should look like:
# right!  
for x in "$ @";  do echo "parameter: '$x'" done

$ .§myscript 'arg 1' arg2 arg3  
parameter: 'arg 1' parameter: 'arg2' parameter: 'arg3'

24. function foo ()

In some shells this works, but not in all. Never combine a keyword function with brackets () when defining a function.

Some versions of bash allow you to use both function , and () , at the same time () , but you cannot do this in any other shell. Some interpreters, however, will perceive function foo , but for maximum compatibility it is better to use:
 foo () { ...  

25. echo "~"

A tilde expansion only happens when the ~ character is not surrounded by quotation marks. This example echo will output ~ to stdout, instead of listing the user home directory.

Escaping variables with paths that must be expressed relative to the home directory should be done using $HOME instead ~ ...
 "~ / dir with spaces" # "~ / dir with spaces"  
~ "/ dir with spaces" # "~ / dir with spaces"  
~ / "dir with spaces" # "/ home / my photos / dir with spaces"  
"$ HOME / dir with spaces" # "/ home / my photos / dir with spaces"

26. local varname = $ (command)

By defining a local variable in a function, it local itself works as a command. Sometimes this may inexplicably interact with the rest of the string. For example, if in the next command you want to receive the return code ($?) Of the substituted command, you will not receive it: the return code of the local command overlaps it.

Therefore, these commands are best separated:
local varname  
varname = $ (command)  
rc = $?