mirror of
https://github.com/flokoe/bash-hackers-wiki.git
synced 2024-11-01 14:53:06 +01:00
283 lines
7.2 KiB
Markdown
283 lines
7.2 KiB
Markdown
# Beginner Mistakes
|
|
|
|
![](keywords>bash shell scripting pitfalls traps beginners)
|
|
|
|
Here are some typical traps:
|
|
|
|
## Script execution
|
|
|
|
### Your perfect Bash script executes with syntax errors
|
|
|
|
If you write Bash scripts with Bash specific syntax and features, run
|
|
them with [Bash]{.underline}, and run them with Bash in [native
|
|
mode]{.underline}.
|
|
|
|
**Wrong**:
|
|
|
|
- no shebang
|
|
- the interpreter used depends on the OS implementation and
|
|
current shell
|
|
- **can** be run by calling bash with the script name as an
|
|
argument, e.g. `bash myscript`
|
|
- `#!/bin/sh` shebang
|
|
- depends on what `/bin/sh` actually is, for a Bash it means
|
|
compatiblity mode, **not** native mode
|
|
|
|
See also:
|
|
|
|
- [Bash startup mode: SH mode](/scripting/bashbehaviour#sh_mode)
|
|
- [Bash run mode: POSIX mode](/scripting/bashbehaviour#posix_run_mode)
|
|
|
|
### Your script named \"test\" doesn\'t execute
|
|
|
|
Give it another name. The executable `test` already exists.
|
|
|
|
In Bash it\'s a builtin. With other shells, it might be an executable
|
|
file. Either way, it\'s bad name choice!
|
|
|
|
Workaround: You can call it using the pathname:
|
|
|
|
/home/user/bin/test
|
|
|
|
## Globbing
|
|
|
|
### Brace expansion is not globbing
|
|
|
|
The following command line is not related to globbing (filename
|
|
expansion):
|
|
|
|
# YOU EXPECT
|
|
# -i1.vob -i2.vob -i3.vob ....
|
|
|
|
echo -i{*.vob,}
|
|
|
|
# YOU GET
|
|
# -i*.vob -i
|
|
|
|
**Why?** The brace expansion is simple text substitution. All possible
|
|
text formed by the prefix, the postfix and the braces themselves are
|
|
generated. In the example, these are only two: `-i*.vob` and `-i`. The
|
|
filename expansion happens **after** that, so there is a chance that
|
|
`-i*.vob` is expanded to a filename - if you have files like
|
|
`-ihello.vob`. But it definitely doesn\'t do what you expected.
|
|
|
|
Please see:
|
|
|
|
- [brace](/syntax/expansion/brace)
|
|
|
|
## Test-command
|
|
|
|
- `if [ $foo ] ...`
|
|
- `if [-d $dir] ...`
|
|
- \...
|
|
|
|
Please see:
|
|
|
|
- [The classic test command -
|
|
pitfalls](/commands/classictest#pitfalls_summarized)
|
|
|
|
## Variables
|
|
|
|
### Setting variables
|
|
|
|
#### The Dollar-Sign
|
|
|
|
There is no `$` (dollar-sign) when you reference the **name** of a
|
|
variable! Bash is not PHP!
|
|
|
|
# THIS IS WRONG!
|
|
$myvar="Hello world!"
|
|
|
|
A variable name preceeded with a dollar-sign always means that the
|
|
variable gets **expanded**. In the example above, it might expand to
|
|
nothing (because it wasn\'t set), effectively resulting in\...
|
|
|
|
="Hello world!"
|
|
|
|
\...which **definitely is wrong**!
|
|
|
|
When you need the **name** of a variable, you write **only the name**,
|
|
for example
|
|
|
|
- (as shown above) to set variables:
|
|
`picture=/usr/share/images/foo.png`
|
|
- to name variables to be used by the `read` builtin command:
|
|
`read picture`
|
|
- to name variables to be unset: `unset picture`
|
|
|
|
When you need the **content** of a variable, you prefix its name with
|
|
**a dollar-sign**, like
|
|
|
|
- echo \"The used picture is: \$picture\"
|
|
|
|
#### Whitespace
|
|
|
|
Putting spaces on either or both sides of the equal-sign (`=`) when
|
|
assigning a value to a variable **will** fail.
|
|
|
|
# INCORRECT 1
|
|
example = Hello
|
|
|
|
# INCORRECT 2
|
|
example= Hello
|
|
|
|
# INCORRECT 3
|
|
example =Hello
|
|
|
|
The only valid form is **no spaces between the variable name and
|
|
assigned value**:
|
|
|
|
# CORRECT 1
|
|
example=Hello
|
|
|
|
# CORRECT 2
|
|
example=" Hello"
|
|
|
|
### Expanding (using) variables
|
|
|
|
A typical beginner\'s trap is quoting.
|
|
|
|
As noted above, when you want to **expand** a variable i.e. \"get the
|
|
content\", the variable name needs to be prefixed with a dollar-sign.
|
|
But, since Bash knows various ways to quote and does word-splitting, the
|
|
result isn\'t always the same.
|
|
|
|
Let\'s define an example variable containing text with spaces:
|
|
|
|
example="Hello world"
|
|
|
|
Used form result number of words
|
|
-------------- --------------- -----------------
|
|
`$example` `Hello world` 2
|
|
`"$example"` `Hello world` 1
|
|
`\$example` `$example` 1
|
|
`'$example'` `$example` 1
|
|
|
|
If you use parameter expansion, you **must** use the **name** (`PATH`)
|
|
of the referenced variables/parameters. i.e. **not** (`$PATH`):
|
|
|
|
# WRONG!
|
|
echo "The first character of PATH is ${$PATH:0:1}"
|
|
|
|
# CORRECT
|
|
echo "The first character of PATH is ${PATH:0:1}"
|
|
|
|
Note that if you are using variables in [arithmetic
|
|
expressions](/syntax/arith_expr), then the bare **name** is allowed:
|
|
|
|
((a=$a+7)) # Add 7 to a
|
|
((a = a + 7)) # Add 7 to a. Identical to the previous command.
|
|
((a += 7)) # Add 7 to a. Identical to the previous command.
|
|
|
|
a=$((a+7)) # POSIX-compatible version of previous code.
|
|
|
|
Please see:
|
|
|
|
- [words](/syntax/words)
|
|
- [quoting](/syntax/quoting)
|
|
- [wordsplit](/syntax/expansion/wordsplit)
|
|
- [pe](/syntax/pe)
|
|
|
|
### Exporting
|
|
|
|
Exporting a variable means giving **newly created** (child-)processes a
|
|
copy of that variable. It does **not** copy a variable created in a
|
|
child process back to the parent process. The following example does
|
|
**not** work, since the variable `hello` is set in a child process (the
|
|
process you execute to start that script `./script.sh`):
|
|
|
|
$ cat script.sh
|
|
export hello=world
|
|
|
|
$ ./script.sh
|
|
$ echo $hello
|
|
$
|
|
|
|
Exporting is one-way. The direction is from parent process to child
|
|
process, not the reverse. The above example **will** work, when you
|
|
don\'t execute the script, but include (\"source\") it:
|
|
|
|
$ source ./script.sh
|
|
$ echo $hello
|
|
world
|
|
$
|
|
|
|
In this case, the export command is of no use.
|
|
|
|
Please see:
|
|
|
|
- [processtree](/scripting/processtree)
|
|
|
|
## Exit codes
|
|
|
|
### Reacting to exit codes
|
|
|
|
If you just want to react to an exit code, regardless of its specific
|
|
value, you **don\'t need** to use `$?` in a test command like this:
|
|
|
|
``` bash
|
|
grep ^root: /etc/passwd >/dev/null 2>&1
|
|
|
|
if [ $? -ne 0 ]; then
|
|
echo "root was not found - check the pub at the corner"
|
|
fi
|
|
```
|
|
|
|
This can be simplified to:
|
|
|
|
``` bash
|
|
if ! grep ^root: /etc/passwd >/dev/null 2>&1; then
|
|
echo "root was not found - check the pub at the corner"
|
|
fi
|
|
```
|
|
|
|
Or, simpler yet:
|
|
|
|
``` bash
|
|
grep ^root: /etc/passwd >/dev/null 2>&1 || echo "root was not found - check the pub at the corner"
|
|
```
|
|
|
|
If you need the specific value of `$?`, there\'s no other choice. But if
|
|
you need only a \"true/false\" exit indication, there\'s no need for
|
|
`$?`.
|
|
|
|
See also:
|
|
|
|
- [Exit codes](/scripting/basics#exit_codes)
|
|
|
|
### Output vs. Return Value
|
|
|
|
It\'s important to remember the different ways to run a child command,
|
|
and whether you want the output, the return value, or neither.
|
|
|
|
When you want to run a command (or a pipeline) and save (or print) the
|
|
**output**, whether as a string or an array, you use Bash\'s
|
|
`$(command)` syntax:
|
|
|
|
$(ls -l /tmp)
|
|
newvariable=$(printf "foo")
|
|
|
|
When you want to use the **return value** of a command, just use the
|
|
command, or add ( ) to run a command or pipeline in a subshell:
|
|
|
|
if grep someuser /etc/passwd ; then
|
|
# do something
|
|
fi
|
|
|
|
if ( w | grep someuser | grep sqlplus ) ; then
|
|
# someuser is logged in and running sqlplus
|
|
fi
|
|
|
|
Make sure you\'re using the form you intended:
|
|
|
|
# WRONG!
|
|
if $(grep ERROR /var/log/messages) ; then
|
|
# send alerts
|
|
fi
|
|
|
|
Please see:
|
|
|
|
- [intro](/syntax/ccmd/intro)
|
|
- [cmdsubst](/syntax/expansion/cmdsubst)
|
|
- [grouping_subshell](/syntax/ccmd/grouping_subshell)
|