mirror of
https://github.com/rawiriblundell/wiki.bash-hackers.org
synced 2024-11-01 16:43:08 +01:00
Correct html quote char
This commit is contained in:
parent
0e677b8e52
commit
39591d2ff9
@ -107,7 +107,12 @@ scrape_targets() {
|
|||||||
# This should remove everything above and below our desired Dokuwiki Markup
|
# This should remove everything above and below our desired Dokuwiki Markup
|
||||||
# We also take the opportunity to convert some HTML chars
|
# We also take the opportunity to convert some HTML chars
|
||||||
extract_markup() {
|
extract_markup() {
|
||||||
sed -e '1,/name="sectok"/d' -e '/<\/textarea>/,$d' -e 's/>/>/g' -e 's/</</g' -e 's/&/\&/g' "${1:-/dev/stdin}"
|
sed -e '1,/name="sectok"/d' \
|
||||||
|
-e '/<\/textarea>/,$d' \
|
||||||
|
-e 's/>/>/g' \
|
||||||
|
-e 's/</</g' \
|
||||||
|
-e 's/&/\&/g' \
|
||||||
|
-e 's/"/"/g' "${1:-/dev/stdin}"
|
||||||
}
|
}
|
||||||
|
|
||||||
###### Beyond this point things get a little wishy-washy ######
|
###### Beyond this point things get a little wishy-washy ######
|
||||||
|
6
bash4.md
6
bash4.md
@ -152,9 +152,9 @@ something like
|
|||||||
|
|
||||||
declare -A ASSOC
|
declare -A ASSOC
|
||||||
|
|
||||||
ASSOC[First]="first element"
|
ASSOC[First]="first element"
|
||||||
ASSOC[Hello]="second element"
|
ASSOC[Hello]="second element"
|
||||||
ASSOC[Peter Pan]="A weird guy"
|
ASSOC[Peter Pan]="A weird guy"
|
||||||
|
|
||||||
See [arrays](/syntax/arrays)
|
See [arrays](/syntax/arrays)
|
||||||
|
|
||||||
|
@ -38,11 +38,11 @@ die() {
|
|||||||
while caller $frame; do
|
while caller $frame; do
|
||||||
((++frame));
|
((++frame));
|
||||||
done
|
done
|
||||||
echo "$*"
|
echo "$*"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
f1() { die "*** an error occured ***"; }
|
f1() { die "*** an error occured ***"; }
|
||||||
f2() { f1; }
|
f2() { f1; }
|
||||||
f3() { f2; }
|
f3() { f2; }
|
||||||
|
|
||||||
|
@ -79,11 +79,11 @@ assignment.
|
|||||||
$ declare -f
|
$ declare -f
|
||||||
foo ()
|
foo ()
|
||||||
{
|
{
|
||||||
echo "FOO is BAR"
|
echo "FOO is BAR"
|
||||||
}
|
}
|
||||||
world ()
|
world ()
|
||||||
{
|
{
|
||||||
echo "Hello World!"
|
echo "Hello World!"
|
||||||
}
|
}
|
||||||
|
|
||||||
...or just a specific defined function.
|
...or just a specific defined function.
|
||||||
@ -91,7 +91,7 @@ assignment.
|
|||||||
$ declare -f foo
|
$ declare -f foo
|
||||||
foo ()
|
foo ()
|
||||||
{
|
{
|
||||||
echo "FOO is BAR"
|
echo "FOO is BAR"
|
||||||
}
|
}
|
||||||
|
|
||||||
### Nameref
|
### Nameref
|
||||||
@ -107,15 +107,15 @@ variable whose name is expanded on the RHS.
|
|||||||
typeset -n _result=$1 _arr
|
typeset -n _result=$1 _arr
|
||||||
typeset IFS=+
|
typeset IFS=+
|
||||||
_result=0
|
_result=0
|
||||||
for _arr in "${@:2}"; do # Demonstrate the special property of "for" on a nameref.
|
for _arr in "${@:2}"; do # Demonstrate the special property of "for" on a nameref.
|
||||||
(( _result += ${_arr[*]} ))
|
(( _result += ${_arr[*]} ))
|
||||||
printf '%s = %d\n' "${!_result}" "$_result" # Demonstrate the special property of ${!ref} on a nameref.
|
printf '%s = %d\n' "${!_result}" "$_result" # Demonstrate the special property of ${!ref} on a nameref.
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
a=(1 2 3) b=(6 5 4) c=(2 4 6)
|
a=(1 2 3) b=(6 5 4) c=(2 4 6)
|
||||||
sum total a b c
|
sum total a b c
|
||||||
printf 'Final value of "total" is: %d\n' "$total"
|
printf 'Final value of "total" is: %d\n' "$total"
|
||||||
|
|
||||||
\<div hide\> function sum {
|
\<div hide\> function sum {
|
||||||
|
|
||||||
@ -123,10 +123,10 @@ variable whose name is expanded on the RHS.
|
|||||||
shift
|
shift
|
||||||
typeset IFS=+ _arrx
|
typeset IFS=+ _arrx
|
||||||
_result=0
|
_result=0
|
||||||
for _arrx in "$@"; do # Demonstrate the special property of "for" on a nameref.
|
for _arrx in "$@"; do # Demonstrate the special property of "for" on a nameref.
|
||||||
typeset -n _arr=$_arrx
|
typeset -n _arr=$_arrx
|
||||||
(( _result += ${_arr[*]} ))
|
(( _result += ${_arr[*]} ))
|
||||||
printf '%s = %d\n' "${!_result}" "$_result" # Demonstrate the special property of ${!ref} on a nameref.
|
printf '%s = %d\n' "${!_result}" "$_result" # Demonstrate the special property of ${!ref} on a nameref.
|
||||||
done
|
done
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ the `eval` command below it.
|
|||||||
... arbitrary bash code here ...
|
... arbitrary bash code here ...
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
eval "$myCode"
|
eval "$myCode"
|
||||||
|
|
||||||
### Expansion side-effects
|
### Expansion side-effects
|
||||||
|
|
||||||
@ -41,16 +41,16 @@ This code defines a set of identical functions using the supplied names.
|
|||||||
`eval` is the only way to achieve this effect.
|
`eval` is the only way to achieve this effect.
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
local fun='() { echo "$FUNCNAME"; }' x
|
local fun='() { echo "$FUNCNAME"; }' x
|
||||||
|
|
||||||
for x in {f..n}; do
|
for x in {f..n}; do
|
||||||
eval "${x}${fun}"
|
eval "${x}${fun}"
|
||||||
done
|
done
|
||||||
|
|
||||||
"$@"
|
"$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|
||||||
### Using printf %q
|
### Using printf %q
|
||||||
|
|
||||||
@ -58,8 +58,8 @@ The `printf %q` format string performs shell escaping on its arguments.
|
|||||||
This makes `printf %q` the "anti-eval" - with each pass of a string
|
This makes `printf %q` the "anti-eval" - with each pass of a string
|
||||||
through printf requiring another `eval` to peel off the escaping again.
|
through printf requiring another `eval` to peel off the escaping again.
|
||||||
|
|
||||||
while (( ++n <= 5 )) || ! evalBall="eval $evalBall"; do
|
while (( ++n <= 5 )) || ! evalBall="eval $evalBall"; do
|
||||||
printf -v evalBall 'eval %q' "printf $n;${evalBall-printf '0\n'}"
|
printf -v evalBall 'eval %q' "printf $n;${evalBall-printf '0\n'}"
|
||||||
done
|
done
|
||||||
$evalBall
|
$evalBall
|
||||||
|
|
||||||
@ -78,19 +78,19 @@ application](http://en.wikipedia.org/wiki/Partial_application) using
|
|||||||
`eval`.
|
`eval`.
|
||||||
|
|
||||||
function partial {
|
function partial {
|
||||||
eval shift 2 \; function "$1" \{ "$2" "$(printf '%q ' "${@:3}")" '"$@"; }'
|
eval shift 2 \; function "$1" \{ "$2" "$(printf '%q ' "${@:3}")" '"$@"; }'
|
||||||
}
|
}
|
||||||
|
|
||||||
function repeat {
|
function repeat {
|
||||||
[[ $1 == +([0-9]) ]] || return
|
[[ $1 == +([0-9]) ]] || return
|
||||||
typeset n
|
typeset n
|
||||||
while ((n++ < $1)); do
|
while ((n++ < $1)); do
|
||||||
"${@:2}"
|
"${@:2}"
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
partial print3 repeat 3 printf '%s ' # Create a new function named print3
|
partial print3 repeat 3 printf '%s ' # Create a new function named print3
|
||||||
print3 hi # Print "hi" 3 times
|
print3 hi # Print "hi" 3 times
|
||||||
echo
|
echo
|
||||||
|
|
||||||
This is very easy to do incorrectly and not usually considered idiomatic
|
This is very easy to do incorrectly and not usually considered idiomatic
|
||||||
@ -120,11 +120,11 @@ controlled carefully by the caller is a good way to use it.
|
|||||||
|
|
||||||
<!-- -->
|
<!-- -->
|
||||||
|
|
||||||
$ ( eval a=( a b\\ c d ); printf '<%s> ' "${a[@]}"; echo ) # Only works in Bash.
|
$ ( eval a=( a b\\ c d ); printf '<%s> ' "${a[@]}"; echo ) # Only works in Bash.
|
||||||
<a> <b c> <d>
|
<a> <b c> <d>
|
||||||
$ ( x=a; eval "$x"=( a b\\ c d ); printf '<%s> ' "${a[@]}"; echo ) # Argument is no longer in the form of a valid assignment, therefore ordinary parsing rules apply.
|
$ ( x=a; eval "$x"=( a b\\ c d ); printf '<%s> ' "${a[@]}"; echo ) # Argument is no longer in the form of a valid assignment, therefore ordinary parsing rules apply.
|
||||||
-bash: syntax error near unexpected token `('
|
-bash: syntax error near unexpected token `('
|
||||||
$ ( x=a; eval "$x"'=( a b\ c d )'; printf '<%s> ' "${a[@]}"; echo ) # Proper quoting then gives us the expected results.
|
$ ( x=a; eval "$x"'=( a b\ c d )'; printf '<%s> ' "${a[@]}"; echo ) # Proper quoting then gives us the expected results.
|
||||||
<a> <b c> <d>
|
<a> <b c> <d>
|
||||||
|
|
||||||
We don't know why Bash does this. Since parentheses are metacharacters,
|
We don't know why Bash does this. Since parentheses are metacharacters,
|
||||||
@ -138,7 +138,7 @@ is still subject to all expansions including
|
|||||||
[word-splitting](syntax/expansion/wordsplit) and [pathname
|
[word-splitting](syntax/expansion/wordsplit) and [pathname
|
||||||
expansion](syntax/expansion/glob).
|
expansion](syntax/expansion/glob).
|
||||||
|
|
||||||
$ ( set -x; touch 'x+=(\[[123]\]=*)' 'x+=([3]=yo)'; eval x+=(*); echo "${x[@]}" )
|
$ ( set -x; touch 'x+=(\[[123]\]=*)' 'x+=([3]=yo)'; eval x+=(*); echo "${x[@]}" )
|
||||||
+ touch 'x+=(\[[123]\]=*)' 'x+=([3]=yo)'
|
+ touch 'x+=(\[[123]\]=*)' 'x+=([3]=yo)'
|
||||||
+ eval 'x+=(\[[123]\]=*)' 'x+=([3]=yo)'
|
+ eval 'x+=(\[[123]\]=*)' 'x+=([3]=yo)'
|
||||||
++ x+=(\[[123]\]=*)
|
++ x+=(\[[123]\]=*)
|
||||||
|
@ -38,12 +38,12 @@ shell without executing any program.
|
|||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
myprog=/bin/ls
|
myprog=/bin/ls
|
||||||
echo "This is the wrapper script, it will exec $myprog"
|
echo "This is the wrapper script, it will exec $myprog"
|
||||||
|
|
||||||
# do some vodoo here, probably change the arguments etc.
|
# do some vodoo here, probably change the arguments etc.
|
||||||
# well, stuff a wrapper is there for
|
# well, stuff a wrapper is there for
|
||||||
|
|
||||||
exec "$myprog" "$@"
|
exec "$myprog" "$@"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Open a file as input for the script
|
### Open a file as input for the script
|
||||||
|
@ -35,7 +35,7 @@ An argument of `--` disables further option processing.
|
|||||||
Set the display to use when launching a GUI application (useful during
|
Set the display to use when launching a GUI application (useful during
|
||||||
SSH sessions):
|
SSH sessions):
|
||||||
|
|
||||||
export DISPLAY=":0"
|
export DISPLAY=":0"
|
||||||
|
|
||||||
Set your default text editor (e.g. SublimeText):
|
Set your default text editor (e.g. SublimeText):
|
||||||
|
|
||||||
|
@ -30,15 +30,15 @@ for arithmetic). For this reason, **the [arithmetic compound
|
|||||||
command](/syntax/ccmd/arithmetic_eval) should generally be preferred
|
command](/syntax/ccmd/arithmetic_eval) should generally be preferred
|
||||||
over `let`**.
|
over `let`**.
|
||||||
|
|
||||||
$ let 'b = a' "(a += 3) + $((a = 1)), b++"
|
$ let 'b = a' "(a += 3) + $((a = 1)), b++"
|
||||||
$ echo "$a - $b - $?"
|
$ echo "$a - $b - $?"
|
||||||
4 - 2 - 0
|
4 - 2 - 0
|
||||||
|
|
||||||
Is equivalent to the [arithmetic evaluation compound
|
Is equivalent to the [arithmetic evaluation compound
|
||||||
command](/syntax/ccmd/arithmetic_eval):
|
command](/syntax/ccmd/arithmetic_eval):
|
||||||
|
|
||||||
$ (( b = a, (a += 3) + $((a = 1)), b++ ))
|
$ (( b = a, (a += 3) + $((a = 1)), b++ ))
|
||||||
$ echo "$a - $b - $?"
|
$ echo "$a - $b - $?"
|
||||||
4 - 2 - 0
|
4 - 2 - 0
|
||||||
|
|
||||||
\<WRAP info\> Remember that inside arithmetic evaluation contexts, all
|
\<WRAP info\> Remember that inside arithmetic evaluation contexts, all
|
||||||
@ -57,12 +57,12 @@ assignments to regular builtins are always local even if the variable is
|
|||||||
modified by the builtin.
|
modified by the builtin.
|
||||||
|
|
||||||
~ $ ( y=1+1 let x=y; declare -p x y )
|
~ $ ( y=1+1 let x=y; declare -p x y )
|
||||||
declare -- x="2"
|
declare -- x="2"
|
||||||
bash: declare: y: not found
|
bash: declare: y: not found
|
||||||
|
|
||||||
~ $ ( y=1+1 let x=y++; declare -p x y )
|
~ $ ( y=1+1 let x=y++; declare -p x y )
|
||||||
declare -- x="2"
|
declare -- x="2"
|
||||||
declare -- y="3"
|
declare -- y="3"
|
||||||
|
|
||||||
This can be useful in certain situations where a temporary variable is
|
This can be useful in certain situations where a temporary variable is
|
||||||
needed.
|
needed.
|
||||||
@ -70,7 +70,7 @@ needed.
|
|||||||
## Portability considerations
|
## Portability considerations
|
||||||
|
|
||||||
- the `let` command is not specified by POSIX(r). The portable
|
- the `let` command is not specified by POSIX(r). The portable
|
||||||
alternative is: `[ "$(( <EXPRESSION> ))" -ne 0 ]`. To make
|
alternative is: `[ "$(( <EXPRESSION> ))" -ne 0 ]`. To make
|
||||||
portable scripts simpler and cleaner, `let` can be defined as:
|
portable scripts simpler and cleaner, `let` can be defined as:
|
||||||
`# POSIX
|
`# POSIX
|
||||||
let() {
|
let() {
|
||||||
|
@ -56,7 +56,7 @@ arguments are guaranteed to be passed to a single invocation of the
|
|||||||
command with no wordsplitting, pathname expansion, or other monkey
|
command with no wordsplitting, pathname expansion, or other monkey
|
||||||
business.
|
business.
|
||||||
|
|
||||||
# eix --only-names -IC x11-drivers | { mapfile -t args; emerge -av1 "${args[@]}" <&1; }
|
# eix --only-names -IC x11-drivers | { mapfile -t args; emerge -av1 "${args[@]}" <&1; }
|
||||||
|
|
||||||
Note the use of command grouping to keep the emerge command inside the
|
Note the use of command grouping to keep the emerge command inside the
|
||||||
pipe's subshell and within the scope of "args". Also note the unusual
|
pipe's subshell and within the scope of "args". Also note the unusual
|
||||||
@ -94,7 +94,7 @@ of a function, with the extra words passed to it as arguments. If you're
|
|||||||
going to use callbacks at all, this is probably the best way because it
|
going to use callbacks at all, this is probably the best way because it
|
||||||
allows for easy access to the arguments with no ugly "code in a string".
|
allows for easy access to the arguments with no ugly "code in a string".
|
||||||
|
|
||||||
$ foo() { echo "|$1|"; }; mapfile -n 11 -c 2 -C 'foo' <file
|
$ foo() { echo "|$1|"; }; mapfile -n 11 -c 2 -C 'foo' <file
|
||||||
|2|
|
|2|
|
||||||
|4|
|
|4|
|
||||||
etc..
|
etc..
|
||||||
@ -105,7 +105,7 @@ every line of some input, and then output even and odd lines to separate
|
|||||||
files. This is far from the best possible answer, but hopefully
|
files. This is far from the best possible answer, but hopefully
|
||||||
illustrates the callback behavior:
|
illustrates the callback behavior:
|
||||||
|
|
||||||
$ { printf 'input%s\n' {1..10} | mapfile -c 1 -C '>&$(( (${#x[@]} % 2) + 3 )) printf -- "%.sprefix %s"' x; } 3>outfile0 4>outfile1
|
$ { printf 'input%s\n' {1..10} | mapfile -c 1 -C '>&$(( (${#x[@]} % 2) + 3 )) printf -- "%.sprefix %s"' x; } 3>outfile0 4>outfile1
|
||||||
$ cat outfile{0,1}
|
$ cat outfile{0,1}
|
||||||
prefix input1
|
prefix input1
|
||||||
prefix input3
|
prefix input3
|
||||||
@ -131,7 +131,7 @@ reader. This is quite the hack but illustrates some interesting
|
|||||||
properties of printf -v and mapfile -C (which you should probably never
|
properties of printf -v and mapfile -C (which you should probably never
|
||||||
use in real code).
|
use in real code).
|
||||||
|
|
||||||
$ y=( 'odd[j]' 'even[j++]' ); printf 'input%s\n' {1..10} | { mapfile -tc 1 -C 'printf -v "${y[${#x[@]} % 2]}" -- "%.sprefix %s"' x; printf '%s\n' "${odd[@]}" '' "${even[@]}"; }
|
$ y=( 'odd[j]' 'even[j++]' ); printf 'input%s\n' {1..10} | { mapfile -tc 1 -C 'printf -v "${y[${#x[@]} % 2]}" -- "%.sprefix %s"' x; printf '%s\n' "${odd[@]}" '' "${even[@]}"; }
|
||||||
prefix input1
|
prefix input1
|
||||||
prefix input3
|
prefix input3
|
||||||
prefix input5
|
prefix input5
|
||||||
@ -154,7 +154,7 @@ and returns the record.
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
showRecord() {
|
showRecord() {
|
||||||
printf 'key[%d] = %d, %d\n' "$1" "${vals[@]:keys[$1]*2:2}"
|
printf 'key[%d] = %d, %d\n' "$1" "${vals[@]:keys[$1]*2:2}"
|
||||||
}
|
}
|
||||||
|
|
||||||
parseRecords() {
|
parseRecords() {
|
||||||
@ -167,17 +167,17 @@ and returns the record.
|
|||||||
local n
|
local n
|
||||||
|
|
||||||
_f
|
_f
|
||||||
mapfile -tc2 -C _f "$1"
|
mapfile -tc2 -C _f "$1"
|
||||||
eval "$1"'=("${'"$1"'[@]##*:}")' # Return the array with some modification
|
eval "$1"'=("${'"$1"'[@]##*:}")' # Return the array with some modification
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
local -a keys vals
|
local -a keys vals
|
||||||
parseRecords vals
|
parseRecords vals
|
||||||
showRecord "$1"
|
showRecord "$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$1" <<-"EOF"
|
main "$1" <<-"EOF"
|
||||||
fabric.domain:123
|
fabric.domain:123
|
||||||
routex:1
|
routex:1
|
||||||
routey:2
|
routey:2
|
||||||
|
@ -41,7 +41,7 @@ formatstring may point to are given after that, here, indicated by
|
|||||||
|
|
||||||
Thus, a typical `printf`-call looks like:
|
Thus, a typical `printf`-call looks like:
|
||||||
|
|
||||||
printf "Surname: %s\nName: %s\n" "$SURNAME" "$FIRSTNAME"
|
printf "Surname: %s\nName: %s\n" "$SURNAME" "$FIRSTNAME"
|
||||||
|
|
||||||
where `"Surname: %s\nName: %s\n"` is the format specification, and the
|
where `"Surname: %s\nName: %s\n"` is the format specification, and the
|
||||||
two variables are passed as arguments, the `%s` in the formatstring
|
two variables are passed as arguments, the `%s` in the formatstring
|
||||||
@ -61,9 +61,9 @@ older than Bash 4.1.
|
|||||||
performing expansions into the first non-option argument of printf as
|
performing expansions into the first non-option argument of printf as
|
||||||
this opens up the possibility of an easy code injection vulnerability.
|
this opens up the possibility of an easy code injection vulnerability.
|
||||||
|
|
||||||
$ var='-vx[$(echo hi >&2)]'; printf "$var" hi; declare -p x
|
$ var='-vx[$(echo hi >&2)]'; printf "$var" hi; declare -p x
|
||||||
hi
|
hi
|
||||||
declare -a x='([0]="hi")'
|
declare -a x='([0]="hi")'
|
||||||
|
|
||||||
...where the echo can of course be replaced with any arbitrary command.
|
...where the echo can of course be replaced with any arbitrary command.
|
||||||
If you must, either specify a hard-coded format string or use -- to
|
If you must, either specify a hard-coded format string or use -- to
|
||||||
@ -159,7 +159,7 @@ To be more flexible in the output of numbers and strings, the `printf`
|
|||||||
command allows format modifiers. These are specified **between** the
|
command allows format modifiers. These are specified **between** the
|
||||||
introductory `%` and the character that specifies the format:
|
introductory `%` and the character that specifies the format:
|
||||||
|
|
||||||
printf "%50s\n" "This field is 50 characters wide..."
|
printf "%50s\n" "This field is 50 characters wide..."
|
||||||
|
|
||||||
#### Field and printing modifiers
|
#### Field and printing modifiers
|
||||||
|
|
||||||
@ -191,7 +191,7 @@ The precision for a floating- or double-number can be specified by using
|
|||||||
`<DIGITS>` is an asterisk (`*`), the precision is read from the argument
|
`<DIGITS>` is an asterisk (`*`), the precision is read from the argument
|
||||||
that precedes the number to print, like (prints 4,3000000000):
|
that precedes the number to print, like (prints 4,3000000000):
|
||||||
|
|
||||||
printf "%.*f\n" 10 4,3
|
printf "%.*f\n" 10 4,3
|
||||||
|
|
||||||
The format `.*N` to specify the N'th argument for precision does not
|
The format `.*N` to specify the N'th argument for precision does not
|
||||||
work in Bash.
|
work in Bash.
|
||||||
@ -271,7 +271,7 @@ This small loop prints all numbers from 0 to 127 in
|
|||||||
<!-- -->
|
<!-- -->
|
||||||
|
|
||||||
for ((x=0; x <= 127; x++)); do
|
for ((x=0; x <= 127; x++)); do
|
||||||
printf '%3d | %04o | 0x%02x\n' "$x" "$x" "$x"
|
printf '%3d | %04o | 0x%02x\n' "$x" "$x" "$x"
|
||||||
done
|
done
|
||||||
|
|
||||||
### Ensure well-formatted MAC address
|
### Ensure well-formatted MAC address
|
||||||
@ -280,13 +280,13 @@ This code here will take a common MAC address and rewrite it into a
|
|||||||
well-known format (regarding leading zeros or upper/lowercase of the hex
|
well-known format (regarding leading zeros or upper/lowercase of the hex
|
||||||
digits, ...):
|
digits, ...):
|
||||||
|
|
||||||
the_mac="0:13:ce:7:7a:ad"
|
the_mac="0:13:ce:7:7a:ad"
|
||||||
|
|
||||||
# lowercase hex digits
|
# lowercase hex digits
|
||||||
the_mac="$(printf "%02x:%02x:%02x:%02x:%02x:%02x" 0x${the_mac//:/ 0x})"
|
the_mac="$(printf "%02x:%02x:%02x:%02x:%02x:%02x" 0x${the_mac//:/ 0x})"
|
||||||
|
|
||||||
# or the uppercase-digits variant
|
# or the uppercase-digits variant
|
||||||
the_mac="$(printf "%02X:%02X:%02X:%02X:%02X:%02X" 0x${the_mac//:/ 0x})"
|
the_mac="$(printf "%02X:%02X:%02X:%02X:%02X:%02X" 0x${the_mac//:/ 0x})"
|
||||||
|
|
||||||
### Replacement echo
|
### Replacement echo
|
||||||
|
|
||||||
@ -294,16 +294,16 @@ This code was found in Solaris manpage for echo(1).
|
|||||||
|
|
||||||
Solaris version of `/usr/bin/echo` is equivalent to:
|
Solaris version of `/usr/bin/echo` is equivalent to:
|
||||||
|
|
||||||
printf "%b\n" "$*"
|
printf "%b\n" "$*"
|
||||||
|
|
||||||
Solaris `/usr/ucb/echo` is equivalent to:
|
Solaris `/usr/ucb/echo` is equivalent to:
|
||||||
|
|
||||||
if [ "X$1" = "X-n" ]
|
if [ "X$1" = "X-n" ]
|
||||||
then
|
then
|
||||||
shift
|
shift
|
||||||
printf "%s" "$*"
|
printf "%s" "$*"
|
||||||
else
|
else
|
||||||
printf "%s\n" "$*"
|
printf "%s\n" "$*"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
### prargs Implementation
|
### prargs Implementation
|
||||||
@ -311,14 +311,14 @@ Solaris `/usr/ucb/echo` is equivalent to:
|
|||||||
Working off the replacement echo, here is a terse implementation of
|
Working off the replacement echo, here is a terse implementation of
|
||||||
prargs:
|
prargs:
|
||||||
|
|
||||||
printf '"%b"\n' "$0" "$@" | nl -v0 -s": "
|
printf '"%b"\n' "$0" "$@" | nl -v0 -s": "
|
||||||
|
|
||||||
### repeating a character (for example to print a line)
|
### repeating a character (for example to print a line)
|
||||||
|
|
||||||
A small trick: Combining printf and parameter expansion to draw a line
|
A small trick: Combining printf and parameter expansion to draw a line
|
||||||
|
|
||||||
length=40
|
length=40
|
||||||
printf -v line '%*s' "$length"
|
printf -v line '%*s' "$length"
|
||||||
echo ${line// /-}
|
echo ${line// /-}
|
||||||
|
|
||||||
or:
|
or:
|
||||||
@ -350,7 +350,7 @@ In the following example, the two strings are concatenated by the
|
|||||||
intervening space so that no argument remains to fill the format.
|
intervening space so that no argument remains to fill the format.
|
||||||
|
|
||||||
|
|
||||||
$ echo "Foo" | awk '{ printf "%s\n" $1 }'
|
$ echo "Foo" | awk '{ printf "%s\n" $1 }'
|
||||||
awk: (FILENAME=- FNR=1) fatal: not enough arguments to satisfy format string
|
awk: (FILENAME=- FNR=1) fatal: not enough arguments to satisfy format string
|
||||||
`%s
|
`%s
|
||||||
Foo'
|
Foo'
|
||||||
@ -359,7 +359,7 @@ intervening space so that no argument remains to fill the format.
|
|||||||
Simply replacing the space with a comma and adding parentheses yields
|
Simply replacing the space with a comma and adding parentheses yields
|
||||||
correct awk syntax.
|
correct awk syntax.
|
||||||
|
|
||||||
$ echo "Foo" | awk '{ printf( "%s\n", $1 ) }'
|
$ echo "Foo" | awk '{ printf( "%s\n", $1 ) }'
|
||||||
Foo
|
Foo
|
||||||
|
|
||||||
With appropriate metacharacter escaping the bash printf can be called
|
With appropriate metacharacter escaping the bash printf can be called
|
||||||
@ -367,7 +367,7 @@ from inside awk (as from perl and other languages that support shell
|
|||||||
callout) as long as you don't care about program efficiency or
|
callout) as long as you don't care about program efficiency or
|
||||||
readability.
|
readability.
|
||||||
|
|
||||||
echo "Foo" | awk '{ system( "printf \"%s\\n \" \"" $1 "\"" ) }'
|
echo "Foo" | awk '{ system( "printf \"%s\\n \" \"" $1 "\"" ) }'
|
||||||
Foo
|
Foo
|
||||||
|
|
||||||
## Differences from C, and portability considerations
|
## Differences from C, and portability considerations
|
||||||
@ -395,7 +395,7 @@ readability.
|
|||||||
modifiers specified by POSIX during format string parsing.
|
modifiers specified by POSIX during format string parsing.
|
||||||
|
|
||||||
``` c|builtins/printf.def
|
``` c|builtins/printf.def
|
||||||
#define LENMODS "hjlLtz"
|
#define LENMODS "hjlLtz"
|
||||||
...
|
...
|
||||||
/* skip possible format modifiers */
|
/* skip possible format modifiers */
|
||||||
modstart = fmt;
|
modstart = fmt;
|
||||||
@ -427,15 +427,15 @@ fmt++;
|
|||||||
shift
|
shift
|
||||||
nameref x=$1
|
nameref x=$1
|
||||||
shift
|
shift
|
||||||
x=$(command printf "$@")
|
x=$(command printf "$@")
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
command printf "$@"
|
command printf "$@"
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
builtin cut
|
builtin cut
|
||||||
print $$
|
print $$
|
||||||
printf -v 'foo[2]' '%d\n' "$(cut -d ' ' -f 1 /proc/self/stat)"
|
printf -v 'foo[2]' '%d\n' "$(cut -d ' ' -f 1 /proc/self/stat)"
|
||||||
typeset -p foo
|
typeset -p foo
|
||||||
# 22461
|
# 22461
|
||||||
# typeset -a foo=([2]=22461)
|
# typeset -a foo=([2]=22461)
|
||||||
|
@ -27,8 +27,8 @@ the line as it was read, without stripping pre- and postfix spaces and
|
|||||||
other things!
|
other things!
|
||||||
|
|
||||||
while read -r; do
|
while read -r; do
|
||||||
printf '"%s"\n' "$REPLY"
|
printf '"%s"\n' "$REPLY"
|
||||||
done <<<" a line with prefix and postfix space "
|
done <<<" a line with prefix and postfix space "
|
||||||
|
|
||||||
\</WRAP\>
|
\</WRAP\>
|
||||||
|
|
||||||
@ -108,13 +108,13 @@ spec.
|
|||||||
2012-05-23 13:49:00 ormaaj so that's what read without -r does?
|
2012-05-23 13:49:00 ormaaj so that's what read without -r does?
|
||||||
2012-05-23 13:49:16 geirha no, -r doesn't remove the backslashes
|
2012-05-23 13:49:16 geirha no, -r doesn't remove the backslashes
|
||||||
2012-05-23 13:49:34 ormaaj I thought read <<<'str' was equivalent to read -r <<<$'str'
|
2012-05-23 13:49:34 ormaaj I thought read <<<'str' was equivalent to read -r <<<$'str'
|
||||||
2012-05-23 13:49:38 geirha # read x y <<< 'foo\ bar baz'; echo "<$x><$y>"
|
2012-05-23 13:49:38 geirha # read x y <<< 'foo\ bar baz'; echo "<$x><$y>"
|
||||||
2012-05-23 13:49:40 shbot geirha: <foo bar><baz>
|
2012-05-23 13:49:40 shbot geirha: <foo bar><baz>
|
||||||
2012-05-23 13:50:32 geirha no, read without -r is mostly pointless. Damn bourne
|
2012-05-23 13:50:32 geirha no, read without -r is mostly pointless. Damn bourne
|
||||||
2012-05-23 13:51:08 ormaaj So it's mostly (entirely) used to escape spaces
|
2012-05-23 13:51:08 ormaaj So it's mostly (entirely) used to escape spaces
|
||||||
2012-05-23 13:51:24 ormaaj and insert newlines
|
2012-05-23 13:51:24 ormaaj and insert newlines
|
||||||
2012-05-23 13:51:47 geirha ormaaj: you mostly get the same effect as using \ at the prompt
|
2012-05-23 13:51:47 geirha ormaaj: you mostly get the same effect as using \ at the prompt
|
||||||
2012-05-23 13:52:04 geirha echo \" outputs a " , read x <<< '\"' reads a "
|
2012-05-23 13:52:04 geirha echo \" outputs a " , read x <<< '\"' reads a "
|
||||||
2012-05-23 13:52:32 ormaaj oh weird
|
2012-05-23 13:52:32 ormaaj oh weird
|
||||||
2012-05-23 13:52:46 * ormaaj struggles to think of a point to that...
|
2012-05-23 13:52:46 * ormaaj struggles to think of a point to that...
|
||||||
2012-05-23 13:53:01 geirha ormaaj: ask Bourne :P
|
2012-05-23 13:53:01 geirha ormaaj: ask Bourne :P
|
||||||
@ -130,8 +130,8 @@ from a file and print them on the terminal.
|
|||||||
|
|
||||||
opossum() {
|
opossum() {
|
||||||
while read -r; do
|
while read -r; do
|
||||||
printf "%s\n" "$REPLY"
|
printf "%s\n" "$REPLY"
|
||||||
done <"$1"
|
done <"$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
<u>**Note:**</u> Here, `read -r` and the default `REPLY` is used,
|
<u>**Note:**</u> Here, `read -r` and the default `REPLY` is used,
|
||||||
@ -145,7 +145,7 @@ Remember the MSDOS `pause` command? Here's something similar:
|
|||||||
|
|
||||||
pause() {
|
pause() {
|
||||||
local dummy
|
local dummy
|
||||||
read -s -r -p "Press any key to continue..." -n 1 dummy
|
read -s -r -p "Press any key to continue..." -n 1 dummy
|
||||||
}
|
}
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
@ -160,14 +160,14 @@ Notes:
|
|||||||
|
|
||||||
Read can be used to split a string:
|
Read can be used to split a string:
|
||||||
|
|
||||||
var="one two three"
|
var="one two three"
|
||||||
read -r col1 col2 col3 <<< "$var"
|
read -r col1 col2 col3 <<< "$var"
|
||||||
printf "col1: %s col2: %s col3 %s\n" "$col1" "$col2" "$col3"
|
printf "col1: %s col2: %s col3 %s\n" "$col1" "$col2" "$col3"
|
||||||
|
|
||||||
Take care that you cannot use a pipe:
|
Take care that you cannot use a pipe:
|
||||||
|
|
||||||
echo "$var" | read col1 col2 col3 # does not work!
|
echo "$var" | read col1 col2 col3 # does not work!
|
||||||
printf "col1: %s col2: %s col3 %s\n" "$col1" "$col2" "$col3"
|
printf "col1: %s col2: %s col3 %s\n" "$col1" "$col2" "$col3"
|
||||||
|
|
||||||
Why? because the commands of the pipe run in subshells that cannot
|
Why? because the commands of the pipe run in subshells that cannot
|
||||||
modify the parent shell. As a result, the variables `col1`, `col2` and
|
modify the parent shell. As a result, the variables `col1`, `col2` and
|
||||||
@ -177,8 +177,8 @@ modify the parent shell. As a result, the variables `col1`, `col2` and
|
|||||||
If the variable has more fields than there are variables, the last
|
If the variable has more fields than there are variables, the last
|
||||||
variable get the remaining of the line:
|
variable get the remaining of the line:
|
||||||
|
|
||||||
read col1 col2 col3 <<< "one two three four"
|
read col1 col2 col3 <<< "one two three four"
|
||||||
printf "%s\n" "$col3" #prints three four
|
printf "%s\n" "$col3" #prints three four
|
||||||
|
|
||||||
#### Changing The Separator
|
#### Changing The Separator
|
||||||
|
|
||||||
@ -186,8 +186,8 @@ By default reads separates the line in fields using spaces or tabs. You
|
|||||||
can modify this using the *special variable*
|
can modify this using the *special variable*
|
||||||
[IFS](/syntax/shellvars#IFS), the Internal Field Separator.
|
[IFS](/syntax/shellvars#IFS), the Internal Field Separator.
|
||||||
|
|
||||||
IFS=":" read -r col1 col2 <<< "hello:world"
|
IFS=":" read -r col1 col2 <<< "hello:world"
|
||||||
printf "col1: %s col2: %s\n" "$col1" "$col2"
|
printf "col1: %s col2: %s\n" "$col1" "$col2"
|
||||||
|
|
||||||
Here we use the `var=value command` syntax to set the environment of
|
Here we use the `var=value command` syntax to set the environment of
|
||||||
`read` temporarily. We could have set `IFS` normally, but then we would
|
`read` temporarily. We could have set `IFS` normally, but then we would
|
||||||
@ -198,8 +198,8 @@ The default `IFS` is special in that 2 fields can be separated by one or
|
|||||||
more space or tab. When you set `IFS` to something besides whitespace
|
more space or tab. When you set `IFS` to something besides whitespace
|
||||||
(space or tab), the fields are separated by **exactly** one character:
|
(space or tab), the fields are separated by **exactly** one character:
|
||||||
|
|
||||||
IFS=":" read -r col1 col2 col3 <<< "hello::world"
|
IFS=":" read -r col1 col2 col3 <<< "hello::world"
|
||||||
printf "col1: %s col2: %s col3 %s\n" "$col1" "$col2" "$col3"
|
printf "col1: %s col2: %s col3 %s\n" "$col1" "$col2" "$col3"
|
||||||
|
|
||||||
See how the `::` in the middle infact defines an additional *empty
|
See how the `::` in the middle infact defines an additional *empty
|
||||||
field*.
|
field*.
|
||||||
@ -207,13 +207,13 @@ field*.
|
|||||||
The fields are separated by exactly one character, but the character can
|
The fields are separated by exactly one character, but the character can
|
||||||
be different between each field:
|
be different between each field:
|
||||||
|
|
||||||
IFS=":|@" read -r col1 col2 col3 col4 <<< "hello:world|in@bash"
|
IFS=":|@" read -r col1 col2 col3 col4 <<< "hello:world|in@bash"
|
||||||
printf "col1: %s col2: %s col3 %s col4 %s\n" "$col1" "$col2" "$col3" "$col4"
|
printf "col1: %s col2: %s col3 %s col4 %s\n" "$col1" "$col2" "$col3" "$col4"
|
||||||
|
|
||||||
### Are you sure?
|
### Are you sure?
|
||||||
|
|
||||||
asksure() {
|
asksure() {
|
||||||
echo -n "Are you sure (Y/N)? "
|
echo -n "Are you sure (Y/N)? "
|
||||||
while read -r -n 1 -s answer; do
|
while read -r -n 1 -s answer; do
|
||||||
if [[ $answer = [YyNn] ]]; then
|
if [[ $answer = [YyNn] ]]; then
|
||||||
[[ $answer = [Yy] ]] && retval=0
|
[[ $answer = [Yy] ]] && retval=0
|
||||||
@ -229,16 +229,16 @@ be different between each field:
|
|||||||
|
|
||||||
### using it
|
### using it
|
||||||
if asksure; then
|
if asksure; then
|
||||||
echo "Okay, performing rm -rf / then, master...."
|
echo "Okay, performing rm -rf / then, master...."
|
||||||
else
|
else
|
||||||
echo "Pfff..."
|
echo "Pfff..."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
### Ask for a path with a default value
|
### Ask for a path with a default value
|
||||||
|
|
||||||
<u>**Note:**</u> The `-i` option was introduced with Bash 4
|
<u>**Note:**</u> The `-i` option was introduced with Bash 4
|
||||||
|
|
||||||
read -e -p "Enter the path to the file: " -i "/usr/local/etc/" FILEPATH
|
read -e -p "Enter the path to the file: " -i "/usr/local/etc/" FILEPATH
|
||||||
|
|
||||||
The user will be prompted, he can just accept the default, or edit it.
|
The user will be prompted, he can just accept the default, or edit it.
|
||||||
|
|
||||||
@ -247,8 +247,8 @@ The user will be prompted, he can just accept the default, or edit it.
|
|||||||
Here, `IFS` contains both, a colon and a space. The fields of the
|
Here, `IFS` contains both, a colon and a space. The fields of the
|
||||||
date/time string are recognized correctly.
|
date/time string are recognized correctly.
|
||||||
|
|
||||||
datetime="2008:07:04 00:34:45"
|
datetime="2008:07:04 00:34:45"
|
||||||
IFS=": " read -r year month day hour minute second <<< "$datetime"
|
IFS=": " read -r year month day hour minute second <<< "$datetime"
|
||||||
|
|
||||||
## Portability considerations
|
## Portability considerations
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ There are no options.
|
|||||||
[shift_verbose](internals/shell_options#shift_verbose)
|
[shift_verbose](internals/shell_options#shift_verbose)
|
||||||
[shopt](commands/builtin/shopt) option is enabled. Ksh93, pdksh, posh,
|
[shopt](commands/builtin/shopt) option is enabled. Ksh93, pdksh, posh,
|
||||||
mksh, and dash, all throw useless fatal shell
|
mksh, and dash, all throw useless fatal shell
|
||||||
errors.`$ dash -c 'f() { if shift; then echo "$1"; else echo "no args"; fi; }; f'
|
errors.`$ dash -c 'f() { if shift; then echo "$1"; else echo "no args"; fi; }; f'
|
||||||
dash: 1: shift: can't shift that many
|
dash: 1: shift: can't shift that many
|
||||||
` In most shells, you can work around this problem using the
|
` In most shells, you can work around this problem using the
|
||||||
[command](/commands/builtin/command) builtin to suppress fatal errors
|
[command](/commands/builtin/command) builtin to suppress fatal errors
|
||||||
|
@ -63,12 +63,12 @@ Here's a demonstration of this behavior.
|
|||||||
# Direct recursion depth.
|
# Direct recursion depth.
|
||||||
# Search up the stack for the first non-FUNCNAME[1] and count how deep we are.
|
# Search up the stack for the first non-FUNCNAME[1] and count how deep we are.
|
||||||
callDepth() {
|
callDepth() {
|
||||||
# Strip "main" off the end of FUNCNAME[@] if current function is named "main" and
|
# Strip "main" off the end of FUNCNAME[@] if current function is named "main" and
|
||||||
# Bash added an extra "main" for non-interactive scripts.
|
# Bash added an extra "main" for non-interactive scripts.
|
||||||
if [[ main == !(!("${FUNCNAME[1]}")|!("${FUNCNAME[-1]}")) && $- != *i* ]]; then
|
if [[ main == !(!("${FUNCNAME[1]}")|!("${FUNCNAME[-1]}")) && $- != *i* ]]; then
|
||||||
local -a 'fnames=("${FUNCNAME[@]:1:${#FUNCNAME[@]}-2}")'
|
local -a 'fnames=("${FUNCNAME[@]:1:${#FUNCNAME[@]}-2}")'
|
||||||
else
|
else
|
||||||
local -a 'fnames=("${FUNCNAME[@]:1}")'
|
local -a 'fnames=("${FUNCNAME[@]:1}")'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if (( ! ${#fnames[@]} )); then
|
if (( ! ${#fnames[@]} )); then
|
||||||
@ -86,7 +86,7 @@ Here's a demonstration of this behavior.
|
|||||||
|
|
||||||
# This function is the magic stack walker.
|
# This function is the magic stack walker.
|
||||||
unset2() {
|
unset2() {
|
||||||
unset -v -- "$@"
|
unset -v -- "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
f() {
|
f() {
|
||||||
@ -97,11 +97,11 @@ Here's a demonstration of this behavior.
|
|||||||
f
|
f
|
||||||
else
|
else
|
||||||
trap 'declare -p a' DEBUG
|
trap 'declare -p a' DEBUG
|
||||||
unset2 a # declare -- a="5"
|
unset2 a # declare -- a="5"
|
||||||
unset a a # declare -- a="4"
|
unset a a # declare -- a="4"
|
||||||
unset a # declare -- a="2"
|
unset a # declare -- a="2"
|
||||||
unset a # ./unset-tests: line 44: declare: a: not found
|
unset a # ./unset-tests: line 44: declare: a: not found
|
||||||
: # declare -- a="global scope yo"
|
: # declare -- a="global scope yo"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,11 +112,11 @@ Here's a demonstration of this behavior.
|
|||||||
|
|
||||||
output:
|
output:
|
||||||
|
|
||||||
declare -- a="5"
|
declare -- a="5"
|
||||||
declare -- a="4"
|
declare -- a="4"
|
||||||
declare -- a="2"
|
declare -- a="2"
|
||||||
./unset-tests: line 44: declare: a: not found
|
./unset-tests: line 44: declare: a: not found
|
||||||
declare -- a="global scope yo"
|
declare -- a="global scope yo"
|
||||||
|
|
||||||
Some things to observe:
|
Some things to observe:
|
||||||
|
|
||||||
@ -143,15 +143,15 @@ Like several other Bash builtins that take parameter names, unset
|
|||||||
expands its arguments.
|
expands its arguments.
|
||||||
|
|
||||||
~ $ ( a=({a..d}); unset 'a[2]'; declare -p a )
|
~ $ ( a=({a..d}); unset 'a[2]'; declare -p a )
|
||||||
declare -a a='([0]="a" [1]="b" [3]="d")'
|
declare -a a='([0]="a" [1]="b" [3]="d")'
|
||||||
|
|
||||||
As usual in such cases, it's important to quote the args to avoid
|
As usual in such cases, it's important to quote the args to avoid
|
||||||
accidental results such as globbing.
|
accidental results such as globbing.
|
||||||
|
|
||||||
~ $ ( a=({a..d}) b=a c=d d=1; set -x; unset "${b}["{2..3}-c\]; declare -p a )
|
~ $ ( a=({a..d}) b=a c=d d=1; set -x; unset "${b}["{2..3}-c\]; declare -p a )
|
||||||
+ unset 'a[2-1]' 'a[3-1]'
|
+ unset 'a[2-1]' 'a[3-1]'
|
||||||
+ declare -p a
|
+ declare -p a
|
||||||
declare -a a='([0]="a" [3]="d")'
|
declare -a a='([0]="a" [3]="d")'
|
||||||
|
|
||||||
Of course hard to follow indirection is still possible whenever
|
Of course hard to follow indirection is still possible whenever
|
||||||
arithmetic is involved, also as shown above, even without extra
|
arithmetic is involved, also as shown above, even without extra
|
||||||
|
@ -15,9 +15,9 @@ here by using the command in an if-statement:
|
|||||||
# test if /etc/passwd exists
|
# test if /etc/passwd exists
|
||||||
|
|
||||||
if test -e /etc/passwd; then
|
if test -e /etc/passwd; then
|
||||||
echo "Alright man..." >&2
|
echo "Alright man..." >&2
|
||||||
else
|
else
|
||||||
echo "Yuck! Where is it??" >&2
|
echo "Yuck! Where is it??" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -37,9 +37,9 @@ Let's rewrite the above example to use it:
|
|||||||
# test if /etc/passwd exists
|
# test if /etc/passwd exists
|
||||||
|
|
||||||
if [ -e /etc/passwd ]; then
|
if [ -e /etc/passwd ]; then
|
||||||
echo "Alright man..." >&2
|
echo "Alright man..." >&2
|
||||||
else
|
else
|
||||||
echo "Yuck! Where is it??" >&2
|
echo "Yuck! Where is it??" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -53,12 +53,12 @@ some of your music files:
|
|||||||
|
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
mymusic="/data/music/Van Halen/Van Halen - Right Now.mp3"
|
mymusic="/data/music/Van Halen/Van Halen - Right Now.mp3"
|
||||||
|
|
||||||
if [ -e "$mymusic" ]; then
|
if [ -e "$mymusic" ]; then
|
||||||
echo "Let's rock" >&2
|
echo "Let's rock" >&2
|
||||||
else
|
else
|
||||||
echo "No music today, sorry..." >&2
|
echo "No music today, sorry..." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -75,7 +75,7 @@ filenames =\> **too many arguments!**
|
|||||||
|
|
||||||
<u>**Another common mistake**</u> is to provide too **few** arguments:
|
<u>**Another common mistake**</u> is to provide too **few** arguments:
|
||||||
|
|
||||||
[ "$mystring"!="test" ]
|
[ "$mystring"!="test" ]
|
||||||
|
|
||||||
This provides exactly **one** test-argument to the command. With one
|
This provides exactly **one** test-argument to the command. With one
|
||||||
parameter, it defaults to the `-n` test: It tests if a provided string
|
parameter, it defaults to the `-n` test: It tests if a provided string
|
||||||
@ -201,8 +201,8 @@ These rules may seem complex, but it's not so bad in practice. Knowing
|
|||||||
them might help you to explain some of the "unexplicable" behaviours you
|
them might help you to explain some of the "unexplicable" behaviours you
|
||||||
might encounter:
|
might encounter:
|
||||||
|
|
||||||
var=""
|
var=""
|
||||||
if [ -n $var ]; then echo "var is not empty"; fi
|
if [ -n $var ]; then echo "var is not empty"; fi
|
||||||
|
|
||||||
This code prints "var is not empty", even though `-n something` is
|
This code prints "var is not empty", even though `-n something` is
|
||||||
supposed to be true if `$var` is not empty - **why?**
|
supposed to be true if `$var` is not empty - **why?**
|
||||||
@ -228,8 +228,8 @@ them with the shell `&&` and `||` **list control operators**.
|
|||||||
|
|
||||||
See this:
|
See this:
|
||||||
|
|
||||||
if [ -n "$var"] && [ -e "$var"]; then
|
if [ -n "$var"] && [ -e "$var"]; then
|
||||||
echo "\$var is not null and a file named $var exists!"
|
echo "\$var is not null and a file named $var exists!"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
The return status of AND and OR lists is the exit status of the last
|
The return status of AND and OR lists is the exit status of the last
|
||||||
@ -245,13 +245,13 @@ command executed in the list
|
|||||||
The logical operators AND and OR for the test-command itself are `-a`
|
The logical operators AND and OR for the test-command itself are `-a`
|
||||||
and `-o`, thus:
|
and `-o`, thus:
|
||||||
|
|
||||||
if [ -n "$var" -a -e "$var" ] ; then
|
if [ -n "$var" -a -e "$var" ] ; then
|
||||||
echo "\$var is not null and a file named $var exists"
|
echo "\$var is not null and a file named $var exists"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
They are **not** `&&` or `||`:
|
They are **not** `&&` or `||`:
|
||||||
|
|
||||||
$ if [ -n "/tmp" && -d "/tmp"]; then echo true; fi # DOES NOT WORK
|
$ if [ -n "/tmp" && -d "/tmp"]; then echo true; fi # DOES NOT WORK
|
||||||
bash: [: missing `]'
|
bash: [: missing `]'
|
||||||
|
|
||||||
You might find the error message confusing, `[` does not find the
|
You might find the error message confusing, `[` does not find the
|
||||||
@ -280,12 +280,12 @@ Let's say, we want to check the following two things (AND):
|
|||||||
|
|
||||||
Let's see:
|
Let's see:
|
||||||
|
|
||||||
if [ -z "false" -a -z "$(echo I am executed >&2)" ] ; then ...
|
if [ -z "false" -a -z "$(echo I am executed >&2)" ] ; then ...
|
||||||
|
|
||||||
=\> The arguments are all expanded **before** `test` runs, thus the
|
=\> The arguments are all expanded **before** `test` runs, thus the
|
||||||
echo-command **is executed**.
|
echo-command **is executed**.
|
||||||
|
|
||||||
if [ -z "false" ] && [ -z "$(echo I am not executed >&2)" ]; then...
|
if [ -z "false" ] && [ -z "$(echo I am not executed >&2)" ]; then...
|
||||||
|
|
||||||
=\> Due to the nature of the `&&` list operator, the second test-command
|
=\> Due to the nature of the `&&` list operator, the second test-command
|
||||||
runs only if the first test-command returns true, our echo-command **is
|
runs only if the first test-command returns true, our echo-command **is
|
||||||
@ -305,10 +305,10 @@ the list way (`&&` and `||`):
|
|||||||
That means, **you can get different results**, depending on the manner
|
That means, **you can get different results**, depending on the manner
|
||||||
of use:
|
of use:
|
||||||
|
|
||||||
$ if [ "true" ] || [ -e /does/not/exist ] && [ -e /does/not/exist ]; then echo true; else echo false; fi
|
$ if [ "true" ] || [ -e /does/not/exist ] && [ -e /does/not/exist ]; then echo true; else echo false; fi
|
||||||
false
|
false
|
||||||
|
|
||||||
$ if [ "true" -o -e /does/not/exist -a -e /does/not/exist ]; then echo true; else echo false;fi
|
$ if [ "true" -o -e /does/not/exist -a -e /does/not/exist ]; then echo true; else echo false;fi
|
||||||
true
|
true
|
||||||
|
|
||||||
As a result you have to think about it a little or add precedence
|
As a result you have to think about it a little or add precedence
|
||||||
@ -317,18 +317,18 @@ control (parenthesis).
|
|||||||
For `&&` and `||` parenthesis means (shell-ly) grouping the commands,
|
For `&&` and `||` parenthesis means (shell-ly) grouping the commands,
|
||||||
and since `( ... )` introduces a subshell we will use `{ ... }` instead:
|
and since `( ... )` introduces a subshell we will use `{ ... }` instead:
|
||||||
|
|
||||||
$ if [ "true" ] || { [ -e /does/not/exist ] && [ -e /does/not/exist ] ;} ; then echo true; else echo false; fi
|
$ if [ "true" ] || { [ -e /does/not/exist ] && [ -e /does/not/exist ] ;} ; then echo true; else echo false; fi
|
||||||
true
|
true
|
||||||
|
|
||||||
For the test command, the precedence parenthesis are, as well, `( )`,
|
For the test command, the precedence parenthesis are, as well, `( )`,
|
||||||
but you need to escape or quote them, so that the shell doesn't try to
|
but you need to escape or quote them, so that the shell doesn't try to
|
||||||
interpret them:
|
interpret them:
|
||||||
|
|
||||||
$ if [ \( "true" -o -e /does/not/exist \) -a -e /does/not/exist ]; then echo true; else echo false; fi
|
$ if [ \( "true" -o -e /does/not/exist \) -a -e /does/not/exist ]; then echo true; else echo false; fi
|
||||||
false
|
false
|
||||||
|
|
||||||
# equivalent, but less readable IMHO:
|
# equivalent, but less readable IMHO:
|
||||||
$ if [ '(' "true" -o -e /does/not/exist ')' -a -e /does/not/exist ]; then echo true; else echo false; fi
|
$ if [ '(' "true" -o -e /does/not/exist ')' -a -e /does/not/exist ]; then echo true; else echo false; fi
|
||||||
false
|
false
|
||||||
|
|
||||||
## NOT
|
## NOT
|
||||||
@ -339,12 +339,12 @@ keyword `!` or passing `!` as an argument to `test`.
|
|||||||
Here `!` negates the exit status of the command `test` which is 0
|
Here `!` negates the exit status of the command `test` which is 0
|
||||||
(true), and the else part is executed:
|
(true), and the else part is executed:
|
||||||
|
|
||||||
if ! [ -d '/tmp' ]; then echo "/tmp doesn't exists"; else echo "/tmp exists"; fi
|
if ! [ -d '/tmp' ]; then echo "/tmp doesn't exists"; else echo "/tmp exists"; fi
|
||||||
|
|
||||||
Here the `test` command itself exits with status 1 (false) and the else
|
Here the `test` command itself exits with status 1 (false) and the else
|
||||||
is also executed:
|
is also executed:
|
||||||
|
|
||||||
if [ ! -d '/tmp' ]; then echo "/tmp doesn't exists"; else echo "/tmp exists"; fi
|
if [ ! -d '/tmp' ]; then echo "/tmp doesn't exists"; else echo "/tmp exists"; fi
|
||||||
|
|
||||||
Unlike for AND and OR, both methods for NOT have an identical behaviour,
|
Unlike for AND and OR, both methods for NOT have an identical behaviour,
|
||||||
at least for doing one single test.
|
at least for doing one single test.
|
||||||
@ -368,7 +368,7 @@ which you may have already had yourself**:
|
|||||||
Hi All,
|
Hi All,
|
||||||
|
|
||||||
I've got a script that I'm trying to set up, but it keeps telling me
|
I've got a script that I'm trying to set up, but it keeps telling me
|
||||||
that "[-d command not found". Can someone please explain what is
|
that "[-d command not found". Can someone please explain what is
|
||||||
wrong with this?:
|
wrong with this?:
|
||||||
|
|
||||||
|
|
||||||
@ -381,9 +381,9 @@ which you may have already had yourself**:
|
|||||||
{
|
{
|
||||||
if [-d $i]
|
if [-d $i]
|
||||||
then
|
then
|
||||||
echo "$i is a directory! Yay!"
|
echo "$i is a directory! Yay!"
|
||||||
else
|
else
|
||||||
echo "$i is not a directory!"
|
echo "$i is not a directory!"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
done
|
done
|
||||||
@ -410,7 +410,7 @@ test command:
|
|||||||
> (QUOTED TEXT WAS REMOVED)
|
> (QUOTED TEXT WAS REMOVED)
|
||||||
|
|
||||||
The shell is first and foremost a way to launch other commands. The
|
The shell is first and foremost a way to launch other commands. The
|
||||||
syntax is simply "if" followed by a command-list, (e.g. if /some/foo;
|
syntax is simply "if" followed by a command-list, (e.g. if /some/foo;
|
||||||
or even if cmd1; cmd2; cmd3; then). Plus the '( ... )' syntax is
|
or even if cmd1; cmd2; cmd3; then). Plus the '( ... )' syntax is
|
||||||
already taken by the use of starting a subshell.
|
already taken by the use of starting a subshell.
|
||||||
|
|
||||||
@ -468,28 +468,28 @@ test command:
|
|||||||
the shell before passing them to the (possibly external) command and
|
the shell before passing them to the (possibly external) command and
|
||||||
disappear entirely. This is why test arguments should always be quoted.
|
disappear entirely. This is why test arguments should always be quoted.
|
||||||
|
|
||||||
if test -d "$file"
|
if test -d "$file"
|
||||||
if [ -d "$file" ]
|
if [ -d "$file" ]
|
||||||
|
|
||||||
Actually today test is defined that if only one argument is given as
|
Actually today test is defined that if only one argument is given as
|
||||||
in this case "test FOO" then then test returns true if the argument is
|
in this case "test FOO" then then test returns true if the argument is
|
||||||
non-zero in text length. Because "-d" is non-zero length "test -d" is
|
non-zero in text length. Because "-d" is non-zero length "test -d" is
|
||||||
true. The number of arguments affects how test parses the args. This
|
true. The number of arguments affects how test parses the args. This
|
||||||
avoids a case where depending upon the data may look like a test
|
avoids a case where depending upon the data may look like a test
|
||||||
operator.
|
operator.
|
||||||
|
|
||||||
DATA="something"
|
DATA="something"
|
||||||
if test "$DATA" # true, $DATA is non-zero length
|
if test "$DATA" # true, $DATA is non-zero length
|
||||||
|
|
||||||
DATA=""
|
DATA=""
|
||||||
if test "$DATA" # false, $DATA is zero length
|
if test "$DATA" # false, $DATA is zero length
|
||||||
|
|
||||||
But the problem case is how should test handle an argument that looks
|
But the problem case is how should test handle an argument that looks
|
||||||
like an operator? This used to generate errors but now because it is
|
like an operator? This used to generate errors but now because it is
|
||||||
only one argument is defined to be the same as test -n $DATA.
|
only one argument is defined to be the same as test -n $DATA.
|
||||||
|
|
||||||
DATA="-d"
|
DATA="-d"
|
||||||
if test "$DATA" # true, $DATA is non-zero length
|
if test "$DATA" # true, $DATA is non-zero length
|
||||||
if test -d # true, same as previous case.
|
if test -d # true, same as previous case.
|
||||||
|
|
||||||
Because test and [ are possibly external commands all of the parts of
|
Because test and [ are possibly external commands all of the parts of
|
||||||
@ -500,7 +500,7 @@ test command:
|
|||||||
|
|
||||||
Incorrect use generating unlikely to be intended results:
|
Incorrect use generating unlikely to be intended results:
|
||||||
|
|
||||||
if test 5 > 2 # true, "5" is non-zero length, creates file named "2"
|
if test 5 > 2 # true, "5" is non-zero length, creates file named "2"
|
||||||
|
|
||||||
Intended use:
|
Intended use:
|
||||||
|
|
||||||
@ -556,7 +556,7 @@ entries of a directory, if an entry is a directory (`[ -d "$fn" ]`),
|
|||||||
print its name:
|
print its name:
|
||||||
|
|
||||||
for fn in *; do
|
for fn in *; do
|
||||||
[ -d "$fn" ] && echo "$fn"
|
[ -d "$fn" ] && echo "$fn"
|
||||||
done
|
done
|
||||||
|
|
||||||
## See also
|
## See also
|
||||||
|
@ -63,7 +63,7 @@ The **output** is converted to **base 10** by default
|
|||||||
## Scale And Base
|
## Scale And Base
|
||||||
|
|
||||||
`dc` is a calulator with abitrary precision, by default this precision
|
`dc` is a calulator with abitrary precision, by default this precision
|
||||||
is 0. thus `dc <<< "5 4/p"` prints "1".
|
is 0. thus `dc <<< "5 4/p"` prints "1".
|
||||||
|
|
||||||
We can increase the precision using the `k` command. It pops the value
|
We can increase the precision using the `k` command. It pops the value
|
||||||
at the top of the stack and uses it as the precision argument:
|
at the top of the stack and uses it as the precision argument:
|
||||||
@ -198,7 +198,7 @@ times, or use a macro.
|
|||||||
[dd**] # push a string
|
[dd**] # push a string
|
||||||
sa # save it in register a
|
sa # save it in register a
|
||||||
3 # push 3 on the stack
|
3 # push 3 on the stack
|
||||||
lax # push the string "dd**" on the stack and execute it
|
lax # push the string "dd**" on the stack and execute it
|
||||||
p # print the result
|
p # print the result
|
||||||
4laxp # same operation for 4, in one line
|
4laxp # same operation for 4, in one line
|
||||||
EOF
|
EOF
|
||||||
@ -221,7 +221,7 @@ we are used to reading:
|
|||||||
Some `dc` have `>R <R =R`, GNU `dc` had some more, check your manual.
|
Some `dc` have `>R <R =R`, GNU `dc` had some more, check your manual.
|
||||||
Note that the test "consumes" its operands: the 2 first elements are
|
Note that the test "consumes" its operands: the 2 first elements are
|
||||||
popped off the stack (you can verify that
|
popped off the stack (you can verify that
|
||||||
`dc <<< "[f]sR 2 1 >R 1 2 >R f"` doesn't print anything)
|
`dc <<< "[f]sR 2 1 >R 1 2 >R f"` doesn't print anything)
|
||||||
|
|
||||||
Have you noticed how we can *include* a macro (string) in a macro? and
|
Have you noticed how we can *include* a macro (string) in a macro? and
|
||||||
as `dc` relies on a stack we can, in fact, use the macro recursively
|
as `dc` relies on a stack we can, in fact, use the macro recursively
|
||||||
|
@ -22,9 +22,9 @@ common example is a script that gives the user the option of having
|
|||||||
chatter() {
|
chatter() {
|
||||||
if [[ $verbose ]]; then
|
if [[ $verbose ]]; then
|
||||||
chatter() {
|
chatter() {
|
||||||
echo "$@"
|
echo "$@"
|
||||||
}
|
}
|
||||||
chatter "$@"
|
chatter "$@"
|
||||||
else
|
else
|
||||||
chatter() {
|
chatter() {
|
||||||
:
|
:
|
||||||
@ -32,9 +32,9 @@ common example is a script that gives the user the option of having
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
echo "Waiting for 10 seconds."
|
echo "Waiting for 10 seconds."
|
||||||
for i in {1..10}; do
|
for i in {1..10}; do
|
||||||
chatter "$i"
|
chatter "$i"
|
||||||
sleep 1
|
sleep 1
|
||||||
done
|
done
|
||||||
|
|
||||||
@ -56,35 +56,35 @@ FIXME Add more examples!
|
|||||||
# various versions of -perm /+ blah blah and hacks
|
# various versions of -perm /+ blah blah and hacks
|
||||||
find() {
|
find() {
|
||||||
hash find || { echo 'find not found!'; exit 1; }
|
hash find || { echo 'find not found!'; exit 1; }
|
||||||
# We can be pretty sure "$0" should be executable.
|
# We can be pretty sure "$0" should be executable.
|
||||||
if [[ $(command find "$0" -executable 2> /dev/null) ]]; then
|
if [[ $(command find "$0" -executable 2> /dev/null) ]]; then
|
||||||
unset -f find # We can just use the command find
|
unset -f find # We can just use the command find
|
||||||
elif [[ $(command find "$0" -perm /u+x 2> /dev/null) ]]; then
|
elif [[ $(command find "$0" -perm /u+x 2> /dev/null) ]]; then
|
||||||
find() {
|
find() {
|
||||||
typeset arg args
|
typeset arg args
|
||||||
for arg do
|
for arg do
|
||||||
[[ $arg = -executable ]] && args+=(-perm /u+x) || args+=("$arg")
|
[[ $arg = -executable ]] && args+=(-perm /u+x) || args+=("$arg")
|
||||||
done
|
done
|
||||||
command find "${args[@]}"
|
command find "${args[@]}"
|
||||||
}
|
}
|
||||||
elif [[ $(command find "$0" -perm +u+x 2> /dev/null) ]]; then
|
elif [[ $(command find "$0" -perm +u+x 2> /dev/null) ]]; then
|
||||||
find() {
|
find() {
|
||||||
typeset arg args
|
typeset arg args
|
||||||
for arg do
|
for arg do
|
||||||
[[ $arg = -executable ]] && args+=(-perm +u+x) || args+=("$arg")
|
[[ $arg = -executable ]] && args+=(-perm +u+x) || args+=("$arg")
|
||||||
done
|
done
|
||||||
command find "${args[@]}"
|
command find "${args[@]}"
|
||||||
}
|
}
|
||||||
else # Last resort
|
else # Last resort
|
||||||
find() {
|
find() {
|
||||||
typeset arg args
|
typeset arg args
|
||||||
for arg do
|
for arg do
|
||||||
[[ $arg = -executable ]] && args+=(-exec test -x {} \; -print) || args+=("$arg")
|
[[ $arg = -executable ]] && args+=(-exec test -x {} \; -print) || args+=("$arg")
|
||||||
done
|
done
|
||||||
command find "${args[@]}"
|
command find "${args[@]}"
|
||||||
}
|
}
|
||||||
fi
|
fi
|
||||||
find "$@"
|
find "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
\<code\> \#!/bin/bash \# Using collapsing functions to turn debug
|
\<code\> \#!/bin/bash \# Using collapsing functions to turn debug
|
||||||
|
@ -10,10 +10,10 @@ Bash source command. The file to be sourced should be formated in
|
|||||||
key="value" format, otherwise bash will try to interpret commands:
|
key="value" format, otherwise bash will try to interpret commands:
|
||||||
|
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
echo "Reading config...." >&2
|
echo "Reading config...." >&2
|
||||||
source /etc/cool.cfg
|
source /etc/cool.cfg
|
||||||
echo "Config for the username: $cool_username" >&2
|
echo "Config for the username: $cool_username" >&2
|
||||||
echo "Config for the target host: $cool_host" >&2
|
echo "Config for the target host: $cool_host" >&2
|
||||||
|
|
||||||
So, where do these variables come from? If everything works fine, they
|
So, where do these variables come from? If everything works fine, they
|
||||||
are defined in /etc/cool.cfg which is a file that's sourced into the
|
are defined in /etc/cool.cfg which is a file that's sourced into the
|
||||||
@ -21,8 +21,8 @@ current script or shell. Note: this is **not** the same as executing
|
|||||||
this file as a script! The sourced file most likely contains something
|
this file as a script! The sourced file most likely contains something
|
||||||
like:
|
like:
|
||||||
|
|
||||||
cool_username="guest"
|
cool_username="guest"
|
||||||
cool_host="foo.example.com"
|
cool_host="foo.example.com"
|
||||||
|
|
||||||
These are normal statements understood by Bash, nothing special. Of
|
These are normal statements understood by Bash, nothing special. Of
|
||||||
course (and, a big disadvantage under normal circumstances) the sourced
|
course (and, a big disadvantage under normal circumstances) the sourced
|
||||||
@ -33,10 +33,10 @@ The `source` command also is available under the name `.` (dot). The
|
|||||||
usage of the dot is identical:
|
usage of the dot is identical:
|
||||||
|
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
echo "Reading config...." >&2
|
echo "Reading config...." >&2
|
||||||
. /etc/cool.cfg #note the space between the dot and the leading slash of /etc.cfg
|
. /etc/cool.cfg #note the space between the dot and the leading slash of /etc.cfg
|
||||||
echo "Config for the username: $cool_username" >&2
|
echo "Config for the username: $cool_username" >&2
|
||||||
echo "Config for the target host: $cool_host" >&2
|
echo "Config for the target host: $cool_host" >&2
|
||||||
|
|
||||||
## Per-user configs
|
## Per-user configs
|
||||||
|
|
||||||
@ -46,10 +46,10 @@ the following example, the if/then construct is used to check for the
|
|||||||
existance of a user-specific config:
|
existance of a user-specific config:
|
||||||
|
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
echo "Reading system-wide config...." >&2
|
echo "Reading system-wide config...." >&2
|
||||||
. /etc/cool.cfg
|
. /etc/cool.cfg
|
||||||
if [ -r ~/.coolrc ]; then
|
if [ -r ~/.coolrc ]; then
|
||||||
echo "Reading user config...." >&2
|
echo "Reading user config...." >&2
|
||||||
. ~/.coolrc
|
. ~/.coolrc
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -68,9 +68,9 @@ malicious code:
|
|||||||
username=god_only_knows
|
username=god_only_knows
|
||||||
hostname=www.example.com
|
hostname=www.example.com
|
||||||
password=secret ; echo rm -rf ~/*
|
password=secret ; echo rm -rf ~/*
|
||||||
parameter=foobar && echo "You've bene pwned!";
|
parameter=foobar && echo "You've bene pwned!";
|
||||||
# hey look, weird code follows...
|
# hey look, weird code follows...
|
||||||
echo "I am the skull virus..."
|
echo "I am the skull virus..."
|
||||||
echo rm -fr ~/*
|
echo rm -fr ~/*
|
||||||
mailto=netadmin@example.com
|
mailto=netadmin@example.com
|
||||||
|
|
||||||
@ -88,15 +88,15 @@ filters by description:
|
|||||||
configfile_secured='/tmp/cool.cfg'
|
configfile_secured='/tmp/cool.cfg'
|
||||||
|
|
||||||
# check if the file contains something we don't want
|
# check if the file contains something we don't want
|
||||||
if egrep -q -v '^#|^[^ ]*=[^;]*' "$configfile"; then
|
if egrep -q -v '^#|^[^ ]*=[^;]*' "$configfile"; then
|
||||||
echo "Config file is unclean, cleaning it..." >&2
|
echo "Config file is unclean, cleaning it..." >&2
|
||||||
# filter the original to a new file
|
# filter the original to a new file
|
||||||
egrep '^#|^[^ ]*=[^;&]*' "$configfile" > "$configfile_secured"
|
egrep '^#|^[^ ]*=[^;&]*' "$configfile" > "$configfile_secured"
|
||||||
configfile="$configfile_secured"
|
configfile="$configfile_secured"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# now source it, either the original or the filtered variant
|
# now source it, either the original or the filtered variant
|
||||||
source "$configfile"
|
source "$configfile"
|
||||||
|
|
||||||
**<u>To make clear what it does:</u>** egrep checks if the file contains
|
**<u>To make clear what it does:</u>** egrep checks if the file contains
|
||||||
something we don't want, if yes, egrep filters the file and writes the
|
something we don't want, if yes, egrep filters the file and writes the
|
||||||
|
@ -41,7 +41,7 @@ let's use the more readable, [modern](/syntax/expansion/cmdsubst) `$()`
|
|||||||
construct instead of the old style backticks:
|
construct instead of the old style backticks:
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
sh $ for i in *.zip; do j=$(basename "$i" ".zip"); mkdir $j; cd $j; unzip ../$i; cd ..; done
|
sh $ for i in *.zip; do j=$(basename "$i" ".zip"); mkdir $j; cd $j; unzip ../$i; cd ..; done
|
||||||
```
|
```
|
||||||
|
|
||||||
In Bash we don't need the subshell or the external basename command. See
|
In Bash we don't need the subshell or the external basename command. See
|
||||||
@ -49,7 +49,7 @@ In Bash we don't need the subshell or the external basename command. See
|
|||||||
expansion](/syntax/pe#substring_removal):
|
expansion](/syntax/pe#substring_removal):
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
bash $ for i in *.zip; do j="${i%.zip}"; mkdir $j; cd $j; unzip ../$i; cd ..; done
|
bash $ for i in *.zip; do j="${i%.zip}"; mkdir $j; cd $j; unzip ../$i; cd ..; done
|
||||||
```
|
```
|
||||||
|
|
||||||
Let's keep going:
|
Let's keep going:
|
||||||
@ -69,7 +69,7 @@ in the previous step? Well, if you don't quote `$j`, wordsplitting can
|
|||||||
happen again.
|
happen again.
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
$ mkdir "$j" && cd "$j" && ... && cd ..
|
$ mkdir "$j" && cd "$j" && ... && cd ..
|
||||||
```
|
```
|
||||||
|
|
||||||
That's almost right, but there's one problem -- what happens if `$j`
|
That's almost right, but there's one problem -- what happens if `$j`
|
||||||
@ -78,7 +78,7 @@ directory. That's wrong! `cd -` causes cd to return to the previous
|
|||||||
working directory, so it's a much better choice:
|
working directory, so it's a much better choice:
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
$ mkdir "$j" && cd "$j" && ... && cd -
|
$ mkdir "$j" && cd "$j" && ... && cd -
|
||||||
```
|
```
|
||||||
|
|
||||||
(If it occurred to you that I forgot to check for success after cd -,
|
(If it occurred to you that I forgot to check for success after cd -,
|
||||||
@ -90,17 +90,17 @@ problem.)
|
|||||||
So now we have:
|
So now we have:
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
sh $ for i in *.zip; do j=$(basename "$i" ".zip"); mkdir "$j" && cd "$j" && unzip ../$i && cd -; done
|
sh $ for i in *.zip; do j=$(basename "$i" ".zip"); mkdir "$j" && cd "$j" && unzip ../$i && cd -; done
|
||||||
```
|
```
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
bash $ for i in *.zip; do j="${i%.zip}"; mkdir "$j" && cd "$j" && unzip ../$i && cd -; done
|
bash $ for i in *.zip; do j="${i%.zip}"; mkdir "$j" && cd "$j" && unzip ../$i && cd -; done
|
||||||
```
|
```
|
||||||
|
|
||||||
Let's throw the `unzip` command back in the mix:
|
Let's throw the `unzip` command back in the mix:
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
mkdir "$j" && cd "$j" && unzip ../$i && cd -
|
mkdir "$j" && cd "$j" && unzip ../$i && cd -
|
||||||
```
|
```
|
||||||
|
|
||||||
Well, besides word splitting, there's nothing terribly wrong with this.
|
Well, besides word splitting, there's nothing terribly wrong with this.
|
||||||
@ -110,15 +110,15 @@ implementations I've seen can do it with the -d flag. So we can drop the
|
|||||||
cd commands entirely:
|
cd commands entirely:
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
$ mkdir "$j" && unzip -d "$j" "$i"
|
$ mkdir "$j" && unzip -d "$j" "$i"
|
||||||
```
|
```
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
sh $ for i in *.zip; do j=$(basename "$i" ".zip"); mkdir "$j" && unzip -d "$j" "$i"; done
|
sh $ for i in *.zip; do j=$(basename "$i" ".zip"); mkdir "$j" && unzip -d "$j" "$i"; done
|
||||||
```
|
```
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
bash $ for i in *.zip; do j="${i%.zip}"; mkdir "$j" && unzip -d "$j" "$i"; done
|
bash $ for i in *.zip; do j="${i%.zip}"; mkdir "$j" && unzip -d "$j" "$i"; done
|
||||||
```
|
```
|
||||||
|
|
||||||
There! That's as good as it gets.
|
There! That's as good as it gets.
|
||||||
|
@ -38,10 +38,10 @@ command, `printf` ("help printf"). Shown here as an example Bash
|
|||||||
function to prefix text to file content:
|
function to prefix text to file content:
|
||||||
|
|
||||||
|
|
||||||
# insertHead "$text" "$file"
|
# insertHead "$text" "$file"
|
||||||
|
|
||||||
insertHead() {
|
insertHead() {
|
||||||
printf '%s\n' H 1i "$1" . w | ed -s "$2"
|
printf '%s\n' H 1i "$1" . w | ed -s "$2"
|
||||||
}
|
}
|
||||||
|
|
||||||
**<u>Here-strings</u>**
|
**<u>Here-strings</u>**
|
||||||
@ -221,9 +221,9 @@ prints the result to stdout - `,p`):
|
|||||||
To compare, here's a possible `sed` solution which must use Bash
|
To compare, here's a possible `sed` solution which must use Bash
|
||||||
arithmetic and the external program `wc`:
|
arithmetic and the external program `wc`:
|
||||||
|
|
||||||
sed "$(($(wc -l < FILE1)-1))r FILE2" FILE1
|
sed "$(($(wc -l < FILE1)-1))r FILE2" FILE1
|
||||||
|
|
||||||
# UPDATE here's one which uses GNU sed's "e" parameter for the s-command
|
# UPDATE here's one which uses GNU sed's "e" parameter for the s-command
|
||||||
# it executes the commands found in pattern space. I'll take that as a
|
# it executes the commands found in pattern space. I'll take that as a
|
||||||
# security risk, but well, sometimes GNU > security, you know...
|
# security risk, but well, sometimes GNU > security, you know...
|
||||||
sed '${h;s/.*/cat FILE2/e;G}' FILE1
|
sed '${h;s/.*/cat FILE2/e;G}' FILE1
|
||||||
@ -250,10 +250,10 @@ about it with the g (global) command:
|
|||||||
|
|
||||||
echo $'1\n1\n3' > file
|
echo $'1\n1\n3' > file
|
||||||
|
|
||||||
#replace all lines matching 1 by "replacement"
|
#replace all lines matching 1 by "replacement"
|
||||||
ed -s file <<< $'g/1/s/1/replacement/\n,p'
|
ed -s file <<< $'g/1/s/1/replacement/\n,p'
|
||||||
|
|
||||||
#replace the first line matching 1 by "replacement"
|
#replace the first line matching 1 by "replacement"
|
||||||
#(because it starts searching from the last line)
|
#(because it starts searching from the last line)
|
||||||
ed -s file <<< $'s/1/replacement/\n,p'
|
ed -s file <<< $'s/1/replacement/\n,p'
|
||||||
|
|
||||||
@ -276,7 +276,7 @@ If you want the same behaviour you can use g/foo/ to trick ed. g/foo/
|
|||||||
will apply the command on all lines matching foo, thus the substitution
|
will apply the command on all lines matching foo, thus the substitution
|
||||||
will succeed and ed will not produce an error when foo is not found:
|
will succeed and ed will not produce an error when foo is not found:
|
||||||
|
|
||||||
#Second version will add the line with "something" even if foo is not found
|
#Second version will add the line with "something" even if foo is not found
|
||||||
ed -s file <<< $'H\n1g/foo/s/foo/bar/\na\nsomething\n.\nw'
|
ed -s file <<< $'H\n1g/foo/s/foo/bar/\na\nsomething\n.\nw'
|
||||||
|
|
||||||
In fact, even a substitution that fails after a g/ / command does not
|
In fact, even a substitution that fails after a g/ / command does not
|
||||||
|
@ -143,14 +143,14 @@ doesn't try to parse the positional parameters, but these given words.
|
|||||||
This way, you are able to parse any option set you like, here for
|
This way, you are able to parse any option set you like, here for
|
||||||
example from an array:
|
example from an array:
|
||||||
|
|
||||||
while getopts :f:h opt "${MY_OWN_SET[@]}"; do
|
while getopts :f:h opt "${MY_OWN_SET[@]}"; do
|
||||||
...
|
...
|
||||||
done
|
done
|
||||||
|
|
||||||
A call to `getopts` **without** these additional arguments is
|
A call to `getopts` **without** these additional arguments is
|
||||||
**equivalent** to explicitly calling it with `"$@"`:
|
**equivalent** to explicitly calling it with `"$@"`:
|
||||||
|
|
||||||
getopts ... "$@"
|
getopts ... "$@"
|
||||||
|
|
||||||
### Error Reporting
|
### Error Reporting
|
||||||
|
|
||||||
@ -189,13 +189,13 @@ preceding the whole option string with a colon (`:`):
|
|||||||
``` bash
|
``` bash
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
while getopts ":a" opt; do
|
while getopts ":a" opt; do
|
||||||
case $opt in
|
case $opt in
|
||||||
a)
|
a)
|
||||||
echo "-a was triggered!" >&2
|
echo "-a was triggered!" >&2
|
||||||
;;
|
;;
|
||||||
\?)
|
\?)
|
||||||
echo "Invalid option: -$OPTARG" >&2
|
echo "Invalid option: -$OPTARG" >&2
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
@ -285,17 +285,17 @@ Let's extend our example from above. Just a little bit:
|
|||||||
``` bash
|
``` bash
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
while getopts ":a:" opt; do
|
while getopts ":a:" opt; do
|
||||||
case $opt in
|
case $opt in
|
||||||
a)
|
a)
|
||||||
echo "-a was triggered, Parameter: $OPTARG" >&2
|
echo "-a was triggered, Parameter: $OPTARG" >&2
|
||||||
;;
|
;;
|
||||||
\?)
|
\?)
|
||||||
echo "Invalid option: -$OPTARG" >&2
|
echo "Invalid option: -$OPTARG" >&2
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
:)
|
:)
|
||||||
echo "Option -$OPTARG requires an argument." >&2
|
echo "Option -$OPTARG requires an argument." >&2
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
@ -67,9 +67,9 @@ With mkdir it seems, we have our two steps in one simple operation. A
|
|||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
if mkdir /var/lock/mylock; then
|
if mkdir /var/lock/mylock; then
|
||||||
echo "Locking succeeded" >&2
|
echo "Locking succeeded" >&2
|
||||||
else
|
else
|
||||||
echo "Lock failed - exit" >&2
|
echo "Lock failed - exit" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
```
|
```
|
||||||
@ -103,12 +103,12 @@ Need to write a code example here.
|
|||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
|
|
||||||
if ( set -o noclobber; echo "locked" > "$lockfile") 2> /dev/null; then
|
if ( set -o noclobber; echo "locked" > "$lockfile") 2> /dev/null; then
|
||||||
trap 'rm -f "$lockfile"; exit $?' INT TERM EXIT
|
trap 'rm -f "$lockfile"; exit $?' INT TERM EXIT
|
||||||
echo "Locking succeeded" >&2
|
echo "Locking succeeded" >&2
|
||||||
rm -f "$lockfile"
|
rm -f "$lockfile"
|
||||||
else
|
else
|
||||||
echo "Lock failed - exit" >&2
|
echo "Lock failed - exit" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -136,59 +136,59 @@ the locking process is shown:
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# lock dirs/files
|
# lock dirs/files
|
||||||
LOCKDIR="/tmp/statsgen-lock"
|
LOCKDIR="/tmp/statsgen-lock"
|
||||||
PIDFILE="${LOCKDIR}/PID"
|
PIDFILE="${LOCKDIR}/PID"
|
||||||
|
|
||||||
# exit codes and text
|
# exit codes and text
|
||||||
ENO_SUCCESS=0; ETXT[0]="ENO_SUCCESS"
|
ENO_SUCCESS=0; ETXT[0]="ENO_SUCCESS"
|
||||||
ENO_GENERAL=1; ETXT[1]="ENO_GENERAL"
|
ENO_GENERAL=1; ETXT[1]="ENO_GENERAL"
|
||||||
ENO_LOCKFAIL=2; ETXT[2]="ENO_LOCKFAIL"
|
ENO_LOCKFAIL=2; ETXT[2]="ENO_LOCKFAIL"
|
||||||
ENO_RECVSIG=3; ETXT[3]="ENO_RECVSIG"
|
ENO_RECVSIG=3; ETXT[3]="ENO_RECVSIG"
|
||||||
|
|
||||||
###
|
###
|
||||||
### start locking attempt
|
### start locking attempt
|
||||||
###
|
###
|
||||||
|
|
||||||
trap 'ECODE=$?; echo "[statsgen] Exit: ${ETXT[ECODE]}($ECODE)" >&2' 0
|
trap 'ECODE=$?; echo "[statsgen] Exit: ${ETXT[ECODE]}($ECODE)" >&2' 0
|
||||||
echo -n "[statsgen] Locking: " >&2
|
echo -n "[statsgen] Locking: " >&2
|
||||||
|
|
||||||
if mkdir "${LOCKDIR}" &>/dev/null; then
|
if mkdir "${LOCKDIR}" &>/dev/null; then
|
||||||
|
|
||||||
# lock succeeded, install signal handlers before storing the PID just in case
|
# lock succeeded, install signal handlers before storing the PID just in case
|
||||||
# storing the PID fails
|
# storing the PID fails
|
||||||
trap 'ECODE=$?;
|
trap 'ECODE=$?;
|
||||||
echo "[statsgen] Removing lock. Exit: ${ETXT[ECODE]}($ECODE)" >&2
|
echo "[statsgen] Removing lock. Exit: ${ETXT[ECODE]}($ECODE)" >&2
|
||||||
rm -rf "${LOCKDIR}"' 0
|
rm -rf "${LOCKDIR}"' 0
|
||||||
echo "$$" >"${PIDFILE}"
|
echo "$$" >"${PIDFILE}"
|
||||||
# the following handler will exit the script upon receiving these signals
|
# the following handler will exit the script upon receiving these signals
|
||||||
# the trap on "0" (EXIT) from above will be triggered by this trap's "exit" command!
|
# the trap on "0" (EXIT) from above will be triggered by this trap's "exit" command!
|
||||||
trap 'echo "[statsgen] Killed by a signal." >&2
|
trap 'echo "[statsgen] Killed by a signal." >&2
|
||||||
exit ${ENO_RECVSIG}' 1 2 3 15
|
exit ${ENO_RECVSIG}' 1 2 3 15
|
||||||
echo "success, installed signal handlers"
|
echo "success, installed signal handlers"
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|
||||||
# lock failed, check if the other PID is alive
|
# lock failed, check if the other PID is alive
|
||||||
OTHERPID="$(cat "${PIDFILE}")"
|
OTHERPID="$(cat "${PIDFILE}")"
|
||||||
|
|
||||||
# if cat isn't able to read the file, another instance is probably
|
# if cat isn't able to read the file, another instance is probably
|
||||||
# about to remove the lock -- exit, we're *still* locked
|
# about to remove the lock -- exit, we're *still* locked
|
||||||
# Thanks to Grzegorz Wierzowiecki for pointing out this race condition on
|
# Thanks to Grzegorz Wierzowiecki for pointing out this race condition on
|
||||||
# http://wiki.grzegorz.wierzowiecki.pl/code:mutex-in-bash
|
# http://wiki.grzegorz.wierzowiecki.pl/code:mutex-in-bash
|
||||||
if [ $? != 0 ]; then
|
if [ $? != 0 ]; then
|
||||||
echo "lock failed, PID ${OTHERPID} is active" >&2
|
echo "lock failed, PID ${OTHERPID} is active" >&2
|
||||||
exit ${ENO_LOCKFAIL}
|
exit ${ENO_LOCKFAIL}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! kill -0 $OTHERPID &>/dev/null; then
|
if ! kill -0 $OTHERPID &>/dev/null; then
|
||||||
# lock is stale, remove it and restart
|
# lock is stale, remove it and restart
|
||||||
echo "removing stale lock of nonexistant PID ${OTHERPID}" >&2
|
echo "removing stale lock of nonexistant PID ${OTHERPID}" >&2
|
||||||
rm -rf "${LOCKDIR}"
|
rm -rf "${LOCKDIR}"
|
||||||
echo "[statsgen] restarting myself" >&2
|
echo "[statsgen] restarting myself" >&2
|
||||||
exec "$0" "$@"
|
exec "$0" "$@"
|
||||||
else
|
else
|
||||||
# lock is valid and OTHERPID is active - exit, we're locked!
|
# lock is valid and OTHERPID is active - exit, we're locked!
|
||||||
echo "lock failed, PID ${OTHERPID} is active" >&2
|
echo "lock failed, PID ${OTHERPID} is active" >&2
|
||||||
exit ${ENO_LOCKFAIL}
|
exit ${ENO_LOCKFAIL}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -183,7 +183,7 @@ give the `-v` option:
|
|||||||
You can extract all files, or files (not) matching specific patterns
|
You can extract all files, or files (not) matching specific patterns
|
||||||
from an archive using constructs like:
|
from an archive using constructs like:
|
||||||
|
|
||||||
# "normal" extraction
|
# "normal" extraction
|
||||||
pax -rf myarchive.tar '*.txt'
|
pax -rf myarchive.tar '*.txt'
|
||||||
|
|
||||||
# with inverted pattern
|
# with inverted pattern
|
||||||
@ -201,8 +201,8 @@ command, use:
|
|||||||
|
|
||||||
To copy directory contents to another directory on a remote system, use:
|
To copy directory contents to another directory on a remote system, use:
|
||||||
|
|
||||||
pax -w localdir | ssh user@host "cd distantdest && pax -r -v"
|
pax -w localdir | ssh user@host "cd distantdest && pax -r -v"
|
||||||
pax -w localdir | gzip | ssh user@host "cd distantdir && gunzip | pax -r -v" #compress the sent data
|
pax -w localdir | gzip | ssh user@host "cd distantdir && gunzip | pax -r -v" #compress the sent data
|
||||||
|
|
||||||
These commands create a copy of localdir in distandir (distantdir/dir)
|
These commands create a copy of localdir in distandir (distantdir/dir)
|
||||||
on the remote machine.
|
on the remote machine.
|
||||||
|
@ -359,12 +359,12 @@ the beginning of your script.
|
|||||||
Let's see another use case. We want to read a file line by line, this is
|
Let's see another use case. We want to read a file line by line, this is
|
||||||
easy, we just do:
|
easy, we just do:
|
||||||
|
|
||||||
while read -r line;do echo "$line";done < file
|
while read -r line;do echo "$line";done < file
|
||||||
|
|
||||||
Now, we want, after printing each line, to do a pause, waiting for the
|
Now, we want, after printing each line, to do a pause, waiting for the
|
||||||
user to press a key:
|
user to press a key:
|
||||||
|
|
||||||
while read -r line;do echo "$line"; read -p "Press any key" -n 1;done < file
|
while read -r line;do echo "$line"; read -p "Press any key" -n 1;done < file
|
||||||
|
|
||||||
And, surprise this doesn't work. Why? because the shell descriptor of
|
And, surprise this doesn't work. Why? because the shell descriptor of
|
||||||
the while loop looks like:
|
the while loop looks like:
|
||||||
@ -390,7 +390,7 @@ descriptor from which `read` should read. Cool. Now let's use `exec` to
|
|||||||
get another descriptor:
|
get another descriptor:
|
||||||
|
|
||||||
exec 3<file
|
exec 3<file
|
||||||
while read -u 3 line;do echo "$line"; read -p "Press any key" -n 1;done
|
while read -u 3 line;do echo "$line"; read -p "Press any key" -n 1;done
|
||||||
|
|
||||||
Now the file descriptors look like:
|
Now the file descriptors look like:
|
||||||
|
|
||||||
@ -656,7 +656,7 @@ recommendations:
|
|||||||
# Good!
|
# Good!
|
||||||
{ cmd1 <<<'my input'; cmd2; } >someFile
|
{ cmd1 <<<'my input'; cmd2; } >someFile
|
||||||
|
|
||||||
# Bad. Is the "1" a file descriptor or an argument to cmd? (answer: it's the FD). Is the space after the herestring part of the input data? (answer: No).
|
# Bad. Is the "1" a file descriptor or an argument to cmd? (answer: it's the FD). Is the space after the herestring part of the input data? (answer: No).
|
||||||
# The redirects are also not delimited in any obvious way.
|
# The redirects are also not delimited in any obvious way.
|
||||||
cmd 2>& 1 <<< stuff
|
cmd 2>& 1 <<< stuff
|
||||||
|
|
||||||
|
@ -9,18 +9,18 @@ We have a simple **stat.sh** script:
|
|||||||
|
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
if [ -z "$1" ]
|
if [ -z "$1" ]
|
||||||
then
|
then
|
||||||
DIR=./
|
DIR=./
|
||||||
else
|
else
|
||||||
DIR=$1
|
DIR=$1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Evaluate *.py statistics"
|
echo "Evaluate *.py statistics"
|
||||||
FILES=$(find $DIR -name '*.py' | wc -l)
|
FILES=$(find $DIR -name '*.py' | wc -l)
|
||||||
LINES=$((find $DIR -name '*.py' -print0 | xargs -0 cat) | wc -l)
|
LINES=$((find $DIR -name '*.py' -print0 | xargs -0 cat) | wc -l)
|
||||||
echo "PYTHON FILES: $FILES"
|
echo "PYTHON FILES: $FILES"
|
||||||
echo "PYTHON LINES: $LINES"
|
echo "PYTHON LINES: $LINES"
|
||||||
|
|
||||||
This script evaluate the number of python files and the number of python
|
This script evaluate the number of python files and the number of python
|
||||||
code lines in the files. We can use it like **./stat.sh \<dir\>**
|
code lines in the files. We can use it like **./stat.sh \<dir\>**
|
||||||
|
@ -17,7 +17,7 @@ From `builtins/read.def`:
|
|||||||
``` C
|
``` C
|
||||||
/* If there are no variables, save the text of the line read to the
|
/* If there are no variables, save the text of the line read to the
|
||||||
variable $REPLY. ksh93 strips leading and trailing IFS whitespace,
|
variable $REPLY. ksh93 strips leading and trailing IFS whitespace,
|
||||||
so that `read x ; echo "$x"' and `read ; echo "$REPLY"' behave the
|
so that `read x ; echo "$x"' and `read ; echo "$REPLY"' behave the
|
||||||
same way, but I believe that the difference in behaviors is useful
|
same way, but I believe that the difference in behaviors is useful
|
||||||
enough to not do it. Without the bash behavior, there is no way
|
enough to not do it. Without the bash behavior, there is no way
|
||||||
to read a line completely without interpretation or modification
|
to read a line completely without interpretation or modification
|
||||||
|
@ -249,7 +249,7 @@ For this topic, see also
|
|||||||
| `${PARAMETER//PATTERN/REPLACEMENT}` | 2.0 | |
|
| `${PARAMETER//PATTERN/REPLACEMENT}` | 2.0 | |
|
||||||
| `${PARAMETER:OFFSET:LENGTH}` | 2.0 | |
|
| `${PARAMETER:OFFSET:LENGTH}` | 2.0 | |
|
||||||
| `${!PARAMETER}` (indirection) | 2.0 | |
|
| `${!PARAMETER}` (indirection) | 2.0 | |
|
||||||
| `$"..."` (localized strings) | 2.0 | |
|
| `$"..."` (localized strings) | 2.0 | |
|
||||||
| `$'...'` (ANSI-C-like strings) | 2.0 | |
|
| `$'...'` (ANSI-C-like strings) | 2.0 | |
|
||||||
| `\xNNN` in `$'...'` (and `echo -e`) | 2.02-alpha1 | |
|
| `\xNNN` in `$'...'` (and `echo -e`) | 2.02-alpha1 | |
|
||||||
| `$(< FILENAME)` (file content) | 2.02-alpha1 | |
|
| `$(< FILENAME)` (file content) | 2.02-alpha1 | |
|
||||||
|
@ -27,7 +27,7 @@ The in-file specification of the interpreter of that file, for example:
|
|||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
echo "Hello world..."
|
echo "Hello world..."
|
||||||
```
|
```
|
||||||
|
|
||||||
This is interpreted by the kernel [^1] of your system. In general, if a
|
This is interpreted by the kernel [^1] of your system. In general, if a
|
||||||
@ -164,9 +164,9 @@ specific user is present in /etc/passwd:
|
|||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
if grep ^root /etc/passwd; then
|
if grep ^root /etc/passwd; then
|
||||||
echo "The user root was found"
|
echo "The user root was found"
|
||||||
else
|
else
|
||||||
echo "The user root was not found"
|
echo "The user root was not found"
|
||||||
fi
|
fi
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -176,10 +176,10 @@ brackets are not part of the shell syntax, the left bracket **is** the
|
|||||||
test command!
|
test command!
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
if [ "$mystring" = "Hello world" ]; then
|
if [ "$mystring" = "Hello world" ]; then
|
||||||
echo "Yeah dude, you entered the right words..."
|
echo "Yeah dude, you entered the right words..."
|
||||||
else
|
else
|
||||||
echo "Eeeek - go away..."
|
echo "Eeeek - go away..."
|
||||||
fi
|
fi
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -190,8 +190,8 @@ This lets you execute a command based on whether or not the previous
|
|||||||
command completed successfully:
|
command completed successfully:
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
grep ^root: /etc/passwd >/dev/null || echo "root was not found - check the pub at the corner."
|
grep ^root: /etc/passwd >/dev/null || echo "root was not found - check the pub at the corner."
|
||||||
which vi && echo "Your favourite editor is installed."
|
which vi && echo "Your favourite editor is installed."
|
||||||
```
|
```
|
||||||
|
|
||||||
Please, when your script exits on errors, provide a "FALSE" exit code,
|
Please, when your script exits on errors, provide a "FALSE" exit code,
|
||||||
@ -206,7 +206,7 @@ can help with debugging or tests. Comments start with the \# character
|
|||||||
``` bash
|
``` bash
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# This is a small script to say something.
|
# This is a small script to say something.
|
||||||
echo "Be liberal in what you accept, and conservative in what you send" # say something
|
echo "Be liberal in what you accept, and conservative in what you send" # say something
|
||||||
```
|
```
|
||||||
|
|
||||||
The first thing was already explained, it's the so-called shebang, for
|
The first thing was already explained, it's the so-called shebang, for
|
||||||
@ -227,14 +227,14 @@ a shutdown:
|
|||||||
``` bash
|
``` bash
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Write info mails, do some tasks and bring down the system in a safe way
|
# Write info mails, do some tasks and bring down the system in a safe way
|
||||||
echo "System halt requested" | mail -s "System halt" netadmin@example.com
|
echo "System halt requested" | mail -s "System halt" netadmin@example.com
|
||||||
logger -t SYSHALT "System halt requested"
|
logger -t SYSHALT "System halt requested"
|
||||||
|
|
||||||
##### The following "code block" is effectively ignored
|
##### The following "code block" is effectively ignored
|
||||||
: <<"SOMEWORD"
|
: <<"SOMEWORD"
|
||||||
/etc/init.d/mydatabase clean_stop
|
/etc/init.d/mydatabase clean_stop
|
||||||
mydatabase_dump /var/db/db1 /mnt/fsrv0/backups/db1
|
mydatabase_dump /var/db/db1 /mnt/fsrv0/backups/db1
|
||||||
logger -t SYSHALT "System halt: pre-shutdown actions done, now shutting down the system"
|
logger -t SYSHALT "System halt: pre-shutdown actions done, now shutting down the system"
|
||||||
shutdown -h NOW
|
shutdown -h NOW
|
||||||
SOMEWORD
|
SOMEWORD
|
||||||
##### The ignored codeblock ends here
|
##### The ignored codeblock ends here
|
||||||
@ -312,13 +312,13 @@ echo $foo
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# this will print "external"
|
# this will print "external"
|
||||||
echo $foo
|
echo $foo
|
||||||
|
|
||||||
# this will print "internal"
|
# this will print "internal"
|
||||||
printvalue
|
printvalue
|
||||||
|
|
||||||
# this will print - again - "external"
|
# this will print - again - "external"
|
||||||
echo $foo
|
echo $foo
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -341,10 +341,10 @@ A variable can be tagged to be part of the environment using the
|
|||||||
``` bash
|
``` bash
|
||||||
# create a new variable and set it:
|
# create a new variable and set it:
|
||||||
# -> This is a normal shell variable, not an environment variable!
|
# -> This is a normal shell variable, not an environment variable!
|
||||||
myvariable="Hello world."
|
myvariable="Hello world."
|
||||||
|
|
||||||
# make the variable visible to all child processes:
|
# make the variable visible to all child processes:
|
||||||
# -> Make it an environment variable: "export" it
|
# -> Make it an environment variable: "export" it
|
||||||
export myvariable
|
export myvariable
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -52,20 +52,20 @@ manpage](http://unixhelp.ed.ac.uk/CGI/man-cgi?logger+1)).
|
|||||||
|
|
||||||
Insert **echos** everywhere you can, and print to `stderr`:
|
Insert **echos** everywhere you can, and print to `stderr`:
|
||||||
|
|
||||||
echo "DEBUG: current i=$i" >&2
|
echo "DEBUG: current i=$i" >&2
|
||||||
|
|
||||||
If you read input from **anywhere**, such as a file or [command
|
If you read input from **anywhere**, such as a file or [command
|
||||||
substitution](/syntax/expansion/cmdsubst), print the debug output with
|
substitution](/syntax/expansion/cmdsubst), print the debug output with
|
||||||
literal quotes, to see leading and trailing spaces!
|
literal quotes, to see leading and trailing spaces!
|
||||||
|
|
||||||
pid=$(< fooservice.pid)
|
pid=$(< fooservice.pid)
|
||||||
echo "DEBUG: read from file: pid=\"$pid\"" >&2
|
echo "DEBUG: read from file: pid=\"$pid\"" >&2
|
||||||
|
|
||||||
Bash's [printf](/commands/builtin/printf) command has the `%q` format,
|
Bash's [printf](/commands/builtin/printf) command has the `%q` format,
|
||||||
which is handy for verifying whether strings are what they appear to be.
|
which is handy for verifying whether strings are what they appear to be.
|
||||||
|
|
||||||
foo=$(< inputfile)
|
foo=$(< inputfile)
|
||||||
printf "DEBUG: foo is |%q|\n" "$foo" >&2
|
printf "DEBUG: foo is |%q|\n" "$foo" >&2
|
||||||
# exposes whitespace (such as CRs, see below) and non-printing characters
|
# exposes whitespace (such as CRs, see below) and non-printing characters
|
||||||
|
|
||||||
## Use shell debug output
|
## Use shell debug output
|
||||||
@ -100,7 +100,7 @@ Here's a simple command (a string comparison using the [classic test
|
|||||||
command](/commands/classictest)) executed while in `set -x` mode:
|
command](/commands/classictest)) executed while in `set -x` mode:
|
||||||
|
|
||||||
set -x
|
set -x
|
||||||
foo="bar baz"
|
foo="bar baz"
|
||||||
[ $foo = test ]
|
[ $foo = test ]
|
||||||
|
|
||||||
That fails. Why? Let's see the `xtrace` output:
|
That fails. Why? Let's see the `xtrace` output:
|
||||||
@ -112,7 +112,7 @@ words (which you would have realized if you READ THE ERROR MESSAGES ;)
|
|||||||
). Let's check it...
|
). Let's check it...
|
||||||
|
|
||||||
# next try
|
# next try
|
||||||
[ "$foo" = test ]
|
[ "$foo" = test ]
|
||||||
|
|
||||||
`xtrace` now gives
|
`xtrace` now gives
|
||||||
|
|
||||||
@ -157,10 +157,10 @@ For general debugging purposes you can also define a function and a
|
|||||||
variable to use:
|
variable to use:
|
||||||
|
|
||||||
debugme() {
|
debugme() {
|
||||||
[[ $script_debug = 1 ]] && "$@" || :
|
[[ $script_debug = 1 ]] && "$@" || :
|
||||||
# be sure to append || : or || true here or use return 0, since the return code
|
# be sure to append || : or || true here or use return 0, since the return code
|
||||||
# of this function should always be 0 to not influence anything else with an unwanted
|
# of this function should always be 0 to not influence anything else with an unwanted
|
||||||
# "false" return code (for example the script's exit code if this function is used
|
# "false" return code (for example the script's exit code if this function is used
|
||||||
# as the very last command in the script)
|
# as the very last command in the script)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,9 +171,9 @@ Use it like this:
|
|||||||
script_debug=1
|
script_debug=1
|
||||||
# to turn it off, set script_debug=0
|
# to turn it off, set script_debug=0
|
||||||
|
|
||||||
debugme logger "Sorting the database"
|
debugme logger "Sorting the database"
|
||||||
database_sort
|
database_sort
|
||||||
debugme logger "Finished sorting the database, exit code $?"
|
debugme logger "Finished sorting the database, exit code $?"
|
||||||
|
|
||||||
Of course this can be used to execute something other than echo during
|
Of course this can be used to execute something other than echo during
|
||||||
debugging:
|
debugging:
|
||||||
@ -236,7 +236,7 @@ to be deliberate. Bash 4.0 added an extra message for this:
|
|||||||
|
|
||||||
### Unexpected end of file while looking for matching ...
|
### Unexpected end of file while looking for matching ...
|
||||||
|
|
||||||
script.sh: line 50: unexpected EOF while looking for matching `"'
|
script.sh: line 50: unexpected EOF while looking for matching `"'
|
||||||
script.sh: line 100: syntax error: unexpected end of file
|
script.sh: line 100: syntax error: unexpected end of file
|
||||||
|
|
||||||
This one indicates the double-quote opened in line 50 does not have a
|
This one indicates the double-quote opened in line 50 does not have a
|
||||||
@ -259,8 +259,8 @@ test-command that yielded the error.
|
|||||||
|
|
||||||
### !": event not found
|
### !": event not found
|
||||||
|
|
||||||
$ echo "Hello world!"
|
$ echo "Hello world!"
|
||||||
bash: !": event not found
|
bash: !": event not found
|
||||||
|
|
||||||
This is not an error per se. It happens in interactive shells, when the
|
This is not an error per se. It happens in interactive shells, when the
|
||||||
C-Shell-styled history expansion ("`!searchword`") is enabled. This is
|
C-Shell-styled history expansion ("`!searchword`") is enabled. This is
|
||||||
@ -275,7 +275,7 @@ the default. Disable it like this:
|
|||||||
When this happens during a script **function definition** or on the
|
When this happens during a script **function definition** or on the
|
||||||
commandline, e.g.
|
commandline, e.g.
|
||||||
|
|
||||||
$ foo () { echo "Hello world"; }
|
$ foo () { echo "Hello world"; }
|
||||||
bash: syntax error near unexpected token `('
|
bash: syntax error near unexpected token `('
|
||||||
|
|
||||||
you most likely have an alias defined with the same name as the function
|
you most likely have an alias defined with the same name as the function
|
||||||
@ -323,7 +323,7 @@ carriage return character!):
|
|||||||
|
|
||||||
#!/bin/bash^M
|
#!/bin/bash^M
|
||||||
^M
|
^M
|
||||||
echo "Hello world"^M
|
echo "Hello world"^M
|
||||||
...
|
...
|
||||||
|
|
||||||
Here's what happens because of the `#!/bin/bash^M` in our shebang:
|
Here's what happens because of the `#!/bin/bash^M` in our shebang:
|
||||||
|
@ -85,13 +85,13 @@ There is no `$` (dollar-sign) when you reference the **name** of a
|
|||||||
variable! Bash is not PHP!
|
variable! Bash is not PHP!
|
||||||
|
|
||||||
# THIS IS WRONG!
|
# THIS IS WRONG!
|
||||||
$myvar="Hello world!"
|
$myvar="Hello world!"
|
||||||
|
|
||||||
A variable name preceeded with a dollar-sign always means that the
|
A variable name preceeded with a dollar-sign always means that the
|
||||||
variable gets **expanded**. In the example above, it might expand to
|
variable gets **expanded**. In the example above, it might expand to
|
||||||
nothing (because it wasn't set), effectively resulting in...
|
nothing (because it wasn't set), effectively resulting in...
|
||||||
|
|
||||||
="Hello world!"
|
="Hello world!"
|
||||||
|
|
||||||
...which **definitely is wrong**!
|
...which **definitely is wrong**!
|
||||||
|
|
||||||
@ -129,7 +129,7 @@ assigned value**:
|
|||||||
example=Hello
|
example=Hello
|
||||||
|
|
||||||
# CORRECT 2
|
# CORRECT 2
|
||||||
example=" Hello"
|
example=" Hello"
|
||||||
|
|
||||||
### Expanding (using) variables
|
### Expanding (using) variables
|
||||||
|
|
||||||
@ -142,7 +142,7 @@ result isn't always the same.
|
|||||||
|
|
||||||
Let's define an example variable containing text with spaces:
|
Let's define an example variable containing text with spaces:
|
||||||
|
|
||||||
example="Hello world"
|
example="Hello world"
|
||||||
|
|
||||||
| Used form | result | number of words |
|
| Used form | result | number of words |
|
||||||
|--------------|---------------|-----------------|
|
|--------------|---------------|-----------------|
|
||||||
@ -155,10 +155,10 @@ If you use parameter expansion, you **must** use the **name** (`PATH`)
|
|||||||
of the referenced variables/parameters. i.e. **not** (`$PATH`):
|
of the referenced variables/parameters. i.e. **not** (`$PATH`):
|
||||||
|
|
||||||
# WRONG!
|
# WRONG!
|
||||||
echo "The first character of PATH is ${$PATH:0:1}"
|
echo "The first character of PATH is ${$PATH:0:1}"
|
||||||
|
|
||||||
# CORRECT
|
# CORRECT
|
||||||
echo "The first character of PATH is ${PATH:0:1}"
|
echo "The first character of PATH is ${PATH:0:1}"
|
||||||
|
|
||||||
Note that if you are using variables in [arithmetic
|
Note that if you are using variables in [arithmetic
|
||||||
expressions](/syntax/arith_expr), then the bare **name** is allowed:
|
expressions](/syntax/arith_expr), then the bare **name** is allowed:
|
||||||
@ -217,7 +217,7 @@ value, you **don't need** to use `$?` in a test command like this:
|
|||||||
grep ^root: /etc/passwd >/dev/null 2>&1
|
grep ^root: /etc/passwd >/dev/null 2>&1
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "root was not found - check the pub at the corner"
|
echo "root was not found - check the pub at the corner"
|
||||||
fi
|
fi
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -225,14 +225,14 @@ This can be simplified to:
|
|||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
if ! grep ^root: /etc/passwd >/dev/null 2>&1; then
|
if ! grep ^root: /etc/passwd >/dev/null 2>&1; then
|
||||||
echo "root was not found - check the pub at the corner"
|
echo "root was not found - check the pub at the corner"
|
||||||
fi
|
fi
|
||||||
```
|
```
|
||||||
|
|
||||||
Or, simpler yet:
|
Or, simpler yet:
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
grep ^root: /etc/passwd >/dev/null 2>&1 || echo "root was not found - check the pub at the corner"
|
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
|
If you need the specific value of `$?`, there's no other choice. But if
|
||||||
@ -252,7 +252,7 @@ When you want to run a command (or a pipeline) and save (or print) the
|
|||||||
syntax:
|
syntax:
|
||||||
|
|
||||||
$(ls -l /tmp)
|
$(ls -l /tmp)
|
||||||
newvariable=$(printf "foo")
|
newvariable=$(printf "foo")
|
||||||
|
|
||||||
When you want to use the **return value** of a command, just use the
|
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:
|
command, or add ( ) to run a command or pipeline in a subshell:
|
||||||
|
@ -134,7 +134,7 @@ exit code, a 100% equivalent construct would be:
|
|||||||
...
|
...
|
||||||
|
|
||||||
# portable equivalent command
|
# portable equivalent command
|
||||||
if [ "$((MATH))" -ne 0 ]; then
|
if [ "$((MATH))" -ne 0 ]; then
|
||||||
...
|
...
|
||||||
|
|
||||||
Quotes around the arithmetic expansion `$((MATH))` should not be
|
Quotes around the arithmetic expansion `$((MATH))` should not be
|
||||||
@ -191,10 +191,10 @@ awk) or 'nawk'. 'oawk' (i.e. Original/Old awk) does not have the rand()
|
|||||||
or srand() functions, so is best avoided.
|
or srand() functions, so is best avoided.
|
||||||
|
|
||||||
# 'gawk' can produce random numbers using srand(). In this example, 10 integers between 1 and 500:
|
# 'gawk' can produce random numbers using srand(). In this example, 10 integers between 1 and 500:
|
||||||
randpm=$(gawk -v min=1 -v max=500 -v nNum=10 'BEGIN { srand(systime() + PROCINFO["pid"]); for (i = 0; i < nNum; ++i) {print int(min + rand() * (max - min)} }')
|
randpm=$(gawk -v min=1 -v max=500 -v nNum=10 'BEGIN { srand(systime() + PROCINFO["pid"]); for (i = 0; i < nNum; ++i) {print int(min + rand() * (max - min)} }')
|
||||||
|
|
||||||
# 'nawk' and 'mawk' does the same, but needs a seed to be provided for its rand() function. In this example we use $(date)
|
# 'nawk' and 'mawk' does the same, but needs a seed to be provided for its rand() function. In this example we use $(date)
|
||||||
randpm=$(mawk -v min=1 -v max=500 -v nNum=10 -v seed="$(date +%Y%M%d%H%M%S)" 'BEGIN { srand(seed); for (i = 0; i < nNum; ++i) {print int(min + rand() * (max - min)} }')
|
randpm=$(mawk -v min=1 -v max=500 -v nNum=10 -v seed="$(date +%Y%M%d%H%M%S)" 'BEGIN { srand(seed); for (i = 0; i < nNum; ++i) {print int(min + rand() * (max - min)} }')
|
||||||
|
|
||||||
*Yes, I'm not an `awk` expert, so please correct it, rather than
|
*Yes, I'm not an `awk` expert, so please correct it, rather than
|
||||||
complaining about possible stupid code!*
|
complaining about possible stupid code!*
|
||||||
@ -232,14 +232,14 @@ For example, to check if the command `ls` is available in a location
|
|||||||
accessible by `PATH`:
|
accessible by `PATH`:
|
||||||
|
|
||||||
if hash ls >/dev/null 2>&1; then
|
if hash ls >/dev/null 2>&1; then
|
||||||
echo "ls is available"
|
echo "ls is available"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
Somewhat of a mass-check:
|
Somewhat of a mass-check:
|
||||||
|
|
||||||
for name in ls grep sed awk; do
|
for name in ls grep sed awk; do
|
||||||
if ! hash "$name" >/dev/null 2>&1; then
|
if ! hash "$name" >/dev/null 2>&1; then
|
||||||
echo "FAIL: Missing command '$name'"
|
echo "FAIL: Missing command '$name'"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
@ -257,7 +257,7 @@ For example, to check if the command `sed` is available in a location
|
|||||||
accessible by `PATH`:
|
accessible by `PATH`:
|
||||||
|
|
||||||
if command -v sed >/dev/null 2>&1; then
|
if command -v sed >/dev/null 2>&1; then
|
||||||
echo "sed is available"
|
echo "sed is available"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
[^1]: "portable" doesn't necessarily mean it's POSIX, it can also mean
|
[^1]: "portable" doesn't necessarily mean it's POSIX, it can also mean
|
||||||
|
@ -35,7 +35,7 @@ shell initialization:
|
|||||||
<u>Testscript</u> - it just echos `$0`:
|
<u>Testscript</u> - it just echos `$0`:
|
||||||
|
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
echo "$0"
|
echo "$0"
|
||||||
|
|
||||||
You see, `$0` is always set to the name the script is called with (`>`
|
You see, `$0` is always set to the name the script is called with (`>`
|
||||||
is the prompt...):
|
is the prompt...):
|
||||||
@ -48,7 +48,7 @@ is the prompt...):
|
|||||||
|
|
||||||
However, this isn't true for login shells:
|
However, this isn't true for login shells:
|
||||||
|
|
||||||
> echo "$0"
|
> echo "$0"
|
||||||
-bash
|
-bash
|
||||||
|
|
||||||
In other terms, `$0` is not a positional parameter, it's a special
|
In other terms, `$0` is not a positional parameter, it's a special
|
||||||
@ -85,12 +85,12 @@ go.
|
|||||||
One way is to access specific parameters:
|
One way is to access specific parameters:
|
||||||
|
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
echo "Total number of arguments: $#"
|
echo "Total number of arguments: $#"
|
||||||
echo "Argument 1: $1"
|
echo "Argument 1: $1"
|
||||||
echo "Argument 2: $2"
|
echo "Argument 2: $2"
|
||||||
echo "Argument 3: $3"
|
echo "Argument 3: $3"
|
||||||
echo "Argument 4: $4"
|
echo "Argument 4: $4"
|
||||||
echo "Argument 5: $5"
|
echo "Argument 5: $5"
|
||||||
|
|
||||||
While useful in another situation, this way is lacks flexibility. The
|
While useful in another situation, this way is lacks flexibility. The
|
||||||
maximum number of arguments is a fixedvalue - which is a bad idea if you
|
maximum number of arguments is a fixedvalue - which is a bad idea if you
|
||||||
@ -111,7 +111,7 @@ argument list:
|
|||||||
numargs=$#
|
numargs=$#
|
||||||
for ((i=1 ; i <= numargs ; i++))
|
for ((i=1 ; i <= numargs ; i++))
|
||||||
do
|
do
|
||||||
echo "$1"
|
echo "$1"
|
||||||
shift
|
shift
|
||||||
done
|
done
|
||||||
|
|
||||||
@ -126,7 +126,7 @@ a given wordlist. The loop uses the positional parameters as a wordlist:
|
|||||||
|
|
||||||
for arg
|
for arg
|
||||||
do
|
do
|
||||||
echo "$arg"
|
echo "$arg"
|
||||||
done
|
done
|
||||||
|
|
||||||
<u>Advantage:</u> The positional parameters will be preserved
|
<u>Advantage:</u> The positional parameters will be preserved
|
||||||
@ -137,9 +137,9 @@ The next method is similar to the first example (the `for` loop), but it
|
|||||||
doesn't test for reaching `$#`. It shifts and checks if `$1` still
|
doesn't test for reaching `$#`. It shifts and checks if `$1` still
|
||||||
expands to something, using the [test command](/commands/classictest):
|
expands to something, using the [test command](/commands/classictest):
|
||||||
|
|
||||||
while [ "$1" ]
|
while [ "$1" ]
|
||||||
do
|
do
|
||||||
echo "$1"
|
echo "$1"
|
||||||
shift
|
shift
|
||||||
done
|
done
|
||||||
|
|
||||||
@ -148,8 +148,8 @@ Looks nice, but has the disadvantage of stopping when `$1` is empty
|
|||||||
may be null), using [parameter expansion for an alternate
|
may be null), using [parameter expansion for an alternate
|
||||||
value](/syntax/pe#use_an_alternate_value):
|
value](/syntax/pe#use_an_alternate_value):
|
||||||
|
|
||||||
while [ "${1+defined}" ]; do
|
while [ "${1+defined}" ]; do
|
||||||
echo "$1"
|
echo "$1"
|
||||||
shift
|
shift
|
||||||
done
|
done
|
||||||
|
|
||||||
@ -226,7 +226,7 @@ decremented.
|
|||||||
|
|
||||||
<u>**Example:**</u> START at the last positional parameter:
|
<u>**Example:**</u> START at the last positional parameter:
|
||||||
|
|
||||||
echo "${@: -1}"
|
echo "${@: -1}"
|
||||||
|
|
||||||
<u>**Attention**</u>: As of Bash 4, a `START` of `0` includes the
|
<u>**Attention**</u>: As of Bash 4, a `START` of `0` includes the
|
||||||
special parameter `$0`, i.e. the shell name or whatever \$0 is set to,
|
special parameter `$0`, i.e. the shell name or whatever \$0 is set to,
|
||||||
@ -240,7 +240,7 @@ only way to set them. The [builtin command, set](/commands/builtin/set)
|
|||||||
may be used to "artificially" change the positional parameters from
|
may be used to "artificially" change the positional parameters from
|
||||||
inside the script or function:
|
inside the script or function:
|
||||||
|
|
||||||
set "This is" my new "set of" positional parameters
|
set "This is" my new "set of" positional parameters
|
||||||
|
|
||||||
# RESULTS IN
|
# RESULTS IN
|
||||||
# $1: This is
|
# $1: This is
|
||||||
@ -282,9 +282,9 @@ rudimentary way to parse your arguments.
|
|||||||
|
|
||||||
while :
|
while :
|
||||||
do
|
do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
-f | --file)
|
-f | --file)
|
||||||
file="$2" # You may want to check validity of $2
|
file="$2" # You may want to check validity of $2
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
-h | --help)
|
-h | --help)
|
||||||
@ -293,31 +293,31 @@ rudimentary way to parse your arguments.
|
|||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
-u | --user)
|
-u | --user)
|
||||||
username="$2" # You may want to check validity of $2
|
username="$2" # You may want to check validity of $2
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
-v | --verbose)
|
-v | --verbose)
|
||||||
# It's better to assign a string, than a number like "verbose=1"
|
# It's better to assign a string, than a number like "verbose=1"
|
||||||
# because if you're debugging the script with "bash -x" code like this:
|
# because if you're debugging the script with "bash -x" code like this:
|
||||||
#
|
#
|
||||||
# if [ "$verbose" ] ...
|
# if [ "$verbose" ] ...
|
||||||
#
|
#
|
||||||
# You will see:
|
# You will see:
|
||||||
#
|
#
|
||||||
# if [ "verbose" ] ...
|
# if [ "verbose" ] ...
|
||||||
#
|
#
|
||||||
# Instead of cryptic
|
# Instead of cryptic
|
||||||
#
|
#
|
||||||
# if [ "1" ] ...
|
# if [ "1" ] ...
|
||||||
#
|
#
|
||||||
verbose="verbose"
|
verbose="verbose"
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--) # End of all options
|
--) # End of all options
|
||||||
shift
|
shift
|
||||||
break;
|
break;
|
||||||
-*)
|
-*)
|
||||||
echo "Error: Unknown option: $1" >&2
|
echo "Error: Unknown option: $1" >&2
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
*) # No more options
|
*) # No more options
|
||||||
@ -346,7 +346,7 @@ options" for `ls` and doesn't change anything after it:
|
|||||||
while [[ $1 ]]
|
while [[ $1 ]]
|
||||||
do
|
do
|
||||||
if ! ((eoo)); then
|
if ! ((eoo)); then
|
||||||
case "$1" in
|
case "$1" in
|
||||||
-a)
|
-a)
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
@ -354,29 +354,29 @@ options" for `ls` and doesn't change anything after it:
|
|||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
-[^-]*a*|-a?*)
|
-[^-]*a*|-a?*)
|
||||||
options+=("${1//a}")
|
options+=("${1//a}")
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--)
|
--)
|
||||||
eoo=1
|
eoo=1
|
||||||
options+=("$1")
|
options+=("$1")
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
options+=("$1")
|
options+=("$1")
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
else
|
else
|
||||||
options+=("$1")
|
options+=("$1")
|
||||||
|
|
||||||
# Another (worse) way of doing the same thing:
|
# Another (worse) way of doing the same thing:
|
||||||
# options=("${options[@]}" "$1")
|
# options=("${options[@]}" "$1")
|
||||||
shift
|
shift
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
/bin/ls "${options[@]}"
|
/bin/ls "${options[@]}"
|
||||||
|
|
||||||
### Using getopts
|
### Using getopts
|
||||||
|
|
||||||
|
@ -96,7 +96,7 @@ into a variable. We run it in a loop here to count input lines:
|
|||||||
counter=0
|
counter=0
|
||||||
|
|
||||||
cat /etc/passwd | while read; do ((counter++)); done
|
cat /etc/passwd | while read; do ((counter++)); done
|
||||||
echo "Lines: $counter"
|
echo "Lines: $counter"
|
||||||
|
|
||||||
What? It's 0? Yes! The number of lines might not be 0, but the variable
|
What? It's 0? Yes! The number of lines might not be 0, but the variable
|
||||||
`$counter` still is 0. Why? Remember the diagram from above? Rewriting
|
`$counter` still is 0. Why? Remember the diagram from above? Rewriting
|
||||||
@ -119,7 +119,7 @@ counter must be the "main shell". For example:
|
|||||||
counter=0
|
counter=0
|
||||||
|
|
||||||
while read; do ((counter++)); done </etc/passwd
|
while read; do ((counter++)); done </etc/passwd
|
||||||
echo "Lines: $counter"
|
echo "Lines: $counter"
|
||||||
|
|
||||||
It's nearly self-explanatory. The `while` loop runs in the **current
|
It's nearly self-explanatory. The `while` loop runs in the **current
|
||||||
shell**, the counter is incremented in the **current shell**, everything
|
shell**, the counter is incremented in the **current shell**, everything
|
||||||
|
@ -114,16 +114,16 @@ In general, every new "layer" gets a new indentation level:
|
|||||||
|
|
||||||
case $input in
|
case $input in
|
||||||
hello)
|
hello)
|
||||||
echo "You said hello"
|
echo "You said hello"
|
||||||
;;
|
;;
|
||||||
bye)
|
bye)
|
||||||
echo "You said bye"
|
echo "You said bye"
|
||||||
if foo; then
|
if foo; then
|
||||||
bar
|
bar
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "You said something weird..."
|
echo "You said something weird..."
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
@ -172,8 +172,8 @@ loop counting variables, etc., ... (in the example: `file`)
|
|||||||
# the prefix 'MY_'
|
# the prefix 'MY_'
|
||||||
MY_LOG_DIRECTORY=/var/adm/
|
MY_LOG_DIRECTORY=/var/adm/
|
||||||
|
|
||||||
for file in "$MY_LOG_DIRECTORY"/*; do
|
for file in "$MY_LOG_DIRECTORY"/*; do
|
||||||
echo "Found Logfile: $file"
|
echo "Found Logfile: $file"
|
||||||
done
|
done
|
||||||
|
|
||||||
### Variable initialization
|
### Variable initialization
|
||||||
@ -190,7 +190,7 @@ via the environment.
|
|||||||
|
|
||||||
The solution is simple and effective: **Initialize them**
|
The solution is simple and effective: **Initialize them**
|
||||||
|
|
||||||
my_input=""
|
my_input=""
|
||||||
my_array=()
|
my_array=()
|
||||||
my_number=0
|
my_number=0
|
||||||
|
|
||||||
@ -216,7 +216,7 @@ expansion, you'll be safe.
|
|||||||
If you need to parse a parameter as a list of words, you can't quote, of
|
If you need to parse a parameter as a list of words, you can't quote, of
|
||||||
course, e.g.
|
course, e.g.
|
||||||
|
|
||||||
list="one two three"
|
list="one two three"
|
||||||
|
|
||||||
# you MUST NOT quote $list here
|
# you MUST NOT quote $list here
|
||||||
for word in $list; do
|
for word in $list; do
|
||||||
@ -340,18 +340,18 @@ missing.
|
|||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
my_needed_commands="sed awk lsof who"
|
my_needed_commands="sed awk lsof who"
|
||||||
|
|
||||||
missing_counter=0
|
missing_counter=0
|
||||||
for needed_command in $my_needed_commands; do
|
for needed_command in $my_needed_commands; do
|
||||||
if ! hash "$needed_command" >/dev/null 2>&1; then
|
if ! hash "$needed_command" >/dev/null 2>&1; then
|
||||||
printf "Command not found in PATH: %s\n" "$needed_command" >&2
|
printf "Command not found in PATH: %s\n" "$needed_command" >&2
|
||||||
((missing_counter++))
|
((missing_counter++))
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if ((missing_counter > 0)); then
|
if ((missing_counter > 0)); then
|
||||||
printf "Minimum %d commands are missing in PATH, aborting\n" "$missing_counter" >&2
|
printf "Minimum %d commands are missing in PATH, aborting\n" "$missing_counter" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -218,9 +218,9 @@ done by the following commands:
|
|||||||
tput smcup
|
tput smcup
|
||||||
clear
|
clear
|
||||||
|
|
||||||
# example "application" follows...
|
# example "application" follows...
|
||||||
read -n1 -p "Press any key to continue..."
|
read -n1 -p "Press any key to continue..."
|
||||||
# example "application" ends here
|
# example "application" ends here
|
||||||
|
|
||||||
# restore
|
# restore
|
||||||
tput rmcup
|
tput rmcup
|
||||||
@ -281,14 +281,14 @@ switch to, to get the other 8 colors.
|
|||||||
|
|
||||||
<u>Directly inside the echo:</u>
|
<u>Directly inside the echo:</u>
|
||||||
|
|
||||||
echo "TPUT is a $(tput setaf 2)nice$(tput setaf 9) and $(tput setaf 5)user friendly$(tput setaf 9) terminal capability database."
|
echo "TPUT is a $(tput setaf 2)nice$(tput setaf 9) and $(tput setaf 5)user friendly$(tput setaf 9) terminal capability database."
|
||||||
|
|
||||||
<u>With preset variables:</u>
|
<u>With preset variables:</u>
|
||||||
|
|
||||||
COL_NORM="$(tput setaf 9)"
|
COL_NORM="$(tput setaf 9)"
|
||||||
COL_RED="$(tput setaf 1)"
|
COL_RED="$(tput setaf 1)"
|
||||||
COL_GREEN="$(tput setaf 2)"
|
COL_GREEN="$(tput setaf 2)"
|
||||||
echo "It's ${COL_RED}red${COL_NORM} and ${COL_GREEN}green${COL_NORM} - have you seen?"
|
echo "It's ${COL_RED}red${COL_NORM} and ${COL_GREEN}green${COL_NORM} - have you seen?"
|
||||||
|
|
||||||
### Misc
|
### Misc
|
||||||
|
|
||||||
@ -303,11 +303,11 @@ switch to, to get the other 8 colors.
|
|||||||
|
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
DATA[0]=" _/ _/ _/ _/ "
|
DATA[0]=" _/ _/ _/ _/ "
|
||||||
DATA[1]=" _/_/_/_/_/ _/_/_/ _/_/_/ _/_/_/ _/_/_/ "
|
DATA[1]=" _/_/_/_/_/ _/_/_/ _/_/_/ _/_/_/ _/_/_/ "
|
||||||
DATA[2]=" _/ _/ _/ _/ _/ _/ _/_/ _/ _/"
|
DATA[2]=" _/ _/ _/ _/ _/ _/ _/_/ _/ _/"
|
||||||
DATA[3]="_/_/_/_/_/ _/ _/ _/ _/ _/_/ _/ _/ "
|
DATA[3]="_/_/_/_/_/ _/ _/ _/ _/ _/_/ _/ _/ "
|
||||||
DATA[4]=" _/ _/ _/_/_/ _/_/_/ _/_/_/ _/ _/ "
|
DATA[4]=" _/ _/ _/_/_/ _/_/_/ _/_/_/ _/ _/ "
|
||||||
|
|
||||||
# virtual coordinate system is X*Y ${#DATA} * 5
|
# virtual coordinate system is X*Y ${#DATA} * 5
|
||||||
|
|
||||||
@ -392,7 +392,7 @@ default), but uses only features that don't make the Bash parser crash.
|
|||||||
}
|
}
|
||||||
|
|
||||||
function colorBox {
|
function colorBox {
|
||||||
(($1==lastclr)) || printf %s "${colrs[lastclr=$1]:=$(tput setaf "$1")}"
|
(($1==lastclr)) || printf %s "${colrs[lastclr=$1]:=$(tput setaf "$1")}"
|
||||||
printf '\u2588'
|
printf '\u2588'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ as a decimal value, you need to expand the parameter and specify base
|
|||||||
# this is interpreted as an octal:
|
# this is interpreted as an octal:
|
||||||
echo $(( x ))
|
echo $(( x ))
|
||||||
|
|
||||||
# this is an invalid digit for base 10 (the "x")...:
|
# this is an invalid digit for base 10 (the "x")...:
|
||||||
echo $(( 10#x ))
|
echo $(( 10#x ))
|
||||||
|
|
||||||
## Different bases
|
## Different bases
|
||||||
@ -88,7 +88,7 @@ mean:
|
|||||||
42
|
42
|
||||||
|
|
||||||
$ echo $((43#H))
|
$ echo $((43#H))
|
||||||
bash: 43#H: value too great for base (error token is "43#H")
|
bash: 43#H: value too great for base (error token is "43#H")
|
||||||
|
|
||||||
If you have no clue what a base is and why there might be other bases,
|
If you have no clue what a base is and why there might be other bases,
|
||||||
and what numbers are and how they are built, then you don't need
|
and what numbers are and how they are built, then you don't need
|
||||||
@ -111,7 +111,7 @@ named parameters, e.g.:
|
|||||||
string=3
|
string=3
|
||||||
|
|
||||||
echo $((test))
|
echo $((test))
|
||||||
# will output "3"!
|
# will output "3"!
|
||||||
|
|
||||||
Of course, in the end, when it finally evaluates to something that is
|
Of course, in the end, when it finally evaluates to something that is
|
||||||
**not** a valid arithmetic expression (newlines, ordinary text, ...)
|
**not** a valid arithmetic expression (newlines, ordinary text, ...)
|
||||||
@ -141,9 +141,9 @@ arithmetic expression to match the "truth" of command exit codes:
|
|||||||
That means, the following `if`-clause will execute the `else`-thread:
|
That means, the following `if`-clause will execute the `else`-thread:
|
||||||
|
|
||||||
if ((0)); then
|
if ((0)); then
|
||||||
echo "true"
|
echo "true"
|
||||||
else
|
else
|
||||||
echo "false"
|
echo "false"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
## Operators
|
## Operators
|
||||||
@ -318,9 +318,9 @@ and all the others, including `set -e` for autoexit on error:
|
|||||||
MY_TEST_FLAG=0
|
MY_TEST_FLAG=0
|
||||||
|
|
||||||
if ((MY_TEST_FLAG)); then
|
if ((MY_TEST_FLAG)); then
|
||||||
echo "MY_TEST_FLAG is ON"
|
echo "MY_TEST_FLAG is ON"
|
||||||
else
|
else
|
||||||
echo "MY_TEST_FLAG is OFF"
|
echo "MY_TEST_FLAG is OFF"
|
||||||
fi
|
fi
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -83,8 +83,8 @@ positional) parameter may be validly referenced using a subscript,
|
|||||||
because in most contexts, referring to the zeroth element of an array is
|
because in most contexts, referring to the zeroth element of an array is
|
||||||
synonymous with referring to the array name without a subscript.
|
synonymous with referring to the array name without a subscript.
|
||||||
|
|
||||||
# "x" is an ordinary non-array parameter.
|
# "x" is an ordinary non-array parameter.
|
||||||
$ x=hi; printf '%s ' "$x" "${x[0]}"; echo "${_[0]}"
|
$ x=hi; printf '%s ' "$x" "${x[0]}"; echo "${_[0]}"
|
||||||
hi hi hi
|
hi hi hi
|
||||||
|
|
||||||
The only exceptions to this rule are in a few cases where the array
|
The only exceptions to this rule are in a few cases where the array
|
||||||
@ -270,7 +270,7 @@ Yes, 12. Fine. You can take this number to walk through the array. Just
|
|||||||
((n_elements=${#sentence[@]}, max_index=n_elements - 1))
|
((n_elements=${#sentence[@]}, max_index=n_elements - 1))
|
||||||
|
|
||||||
for ((i = 0; i <= max_index; i++)); do
|
for ((i = 0; i <= max_index; i++)); do
|
||||||
echo "Element $i: '${sentence[i]}'"
|
echo "Element $i: '${sentence[i]}'"
|
||||||
done
|
done
|
||||||
|
|
||||||
You always have to remember that, it seems newbies have problems
|
You always have to remember that, it seems newbies have problems
|
||||||
@ -291,7 +291,7 @@ think you could just do
|
|||||||
$ echo ${#sentence[@]}
|
$ echo ${#sentence[@]}
|
||||||
1
|
1
|
||||||
# omit calculating max_index as above, and iterate as one-liner
|
# omit calculating max_index as above, and iterate as one-liner
|
||||||
$ for ((i = 0; i < ${#sentence[@]}; i++)); do echo "Element $i: '${sentence[i]}'" ; done
|
$ for ((i = 0; i < ${#sentence[@]}; i++)); do echo "Element $i: '${sentence[i]}'" ; done
|
||||||
Element 0: 'NAMES'
|
Element 0: 'NAMES'
|
||||||
|
|
||||||
Obviously that's wrong. What about
|
Obviously that's wrong. What about
|
||||||
@ -302,16 +302,16 @@ Obviously that's wrong. What about
|
|||||||
|
|
||||||
$ echo ${#sentence[*]}
|
$ echo ${#sentence[*]}
|
||||||
1
|
1
|
||||||
$ for ((i = 0; i < ${#sentence[@]}; i++)); do echo "Element $i: '${sentence[i]}'" ; done
|
$ for ((i = 0; i < ${#sentence[@]}; i++)); do echo "Element $i: '${sentence[i]}'" ; done
|
||||||
Element 0: 'Peter'
|
Element 0: 'Peter'
|
||||||
|
|
||||||
So what's the **right** way? The (slightly ugly) answer is, reuse the
|
So what's the **right** way? The (slightly ugly) answer is, reuse the
|
||||||
enumeration syntax:
|
enumeration syntax:
|
||||||
|
|
||||||
$ unset sentence ; declare -a sentence=("${NAMES[@]}")
|
$ unset sentence ; declare -a sentence=("${NAMES[@]}")
|
||||||
$ echo ${#sentence[@]}
|
$ echo ${#sentence[@]}
|
||||||
4
|
4
|
||||||
$ for ((i = 0; i < ${#sentence[@]}; i++)); do echo "Element $i: '${sentence[i]}'" ; done
|
$ for ((i = 0; i < ${#sentence[@]}; i++)); do echo "Element $i: '${sentence[i]}'" ; done
|
||||||
Element 0: 'Peter'
|
Element 0: 'Peter'
|
||||||
Element 1: 'Anna'
|
Element 1: 'Anna'
|
||||||
Element 2: 'Greg'
|
Element 2: 'Greg'
|
||||||
@ -335,7 +335,7 @@ starting at zero) just is replaced with an arbitrary string:
|
|||||||
in memory like they were declared, it could look like this:
|
in memory like they were declared, it could look like this:
|
||||||
|
|
||||||
# output from 'set' command
|
# output from 'set' command
|
||||||
sentence=([End]="in what you send" [Middle]="you accept, and conservative " [Begin]="Be liberal in what " ["Very end"]="...")
|
sentence=([End]="in what you send" [Middle]="you accept, and conservative " [Begin]="Be liberal in what " ["Very end"]="...")
|
||||||
|
|
||||||
This effectively means, you can get the data back with
|
This effectively means, you can get the data back with
|
||||||
`"${sentence[@]}"`, of course (just like with numerical indexing), but
|
`"${sentence[@]}"`, of course (just like with numerical indexing), but
|
||||||
@ -343,10 +343,10 @@ you can't rely on a specific order. If you want to store ordered data,
|
|||||||
or re-order data, go with numerical indexes. For associative arrays, you
|
or re-order data, go with numerical indexes. For associative arrays, you
|
||||||
usually query known index values:
|
usually query known index values:
|
||||||
|
|
||||||
for element in Begin Middle End "Very end"; do
|
for element in Begin Middle End "Very end"; do
|
||||||
printf "%s" "${sentence[$element]}"
|
printf "%s" "${sentence[$element]}"
|
||||||
done
|
done
|
||||||
printf "\n"
|
printf "\n"
|
||||||
|
|
||||||
**A nice code example:** Checking for duplicate files using an
|
**A nice code example:** Checking for duplicate files using an
|
||||||
associative array indexed with the SHA sum of the files:
|
associative array indexed with the SHA sum of the files:
|
||||||
@ -356,9 +356,9 @@ associative array indexed with the SHA sum of the files:
|
|||||||
unset flist; declare -A flist;
|
unset flist; declare -A flist;
|
||||||
while read -r sum fname; do
|
while read -r sum fname; do
|
||||||
if [[ ${flist[$sum]} ]]; then
|
if [[ ${flist[$sum]} ]]; then
|
||||||
printf 'rm -- "%s" # Same as >%s<\n' "$fname" "${flist[$sum]}"
|
printf 'rm -- "%s" # Same as >%s<\n' "$fname" "${flist[$sum]}"
|
||||||
else
|
else
|
||||||
flist[$sum]="$fname"
|
flist[$sum]="$fname"
|
||||||
fi
|
fi
|
||||||
done < <(find . -type f -exec sha256sum {} +) >rmdups
|
done < <(find . -type f -exec sha256sum {} +) >rmdups
|
||||||
|
|
||||||
@ -370,8 +370,8 @@ arrays, then values are considered as arithmetic for both compound and
|
|||||||
ordinary assignment, and the += operator is modified in the same way as
|
ordinary assignment, and the += operator is modified in the same way as
|
||||||
for ordinary integer variables.
|
for ordinary integer variables.
|
||||||
|
|
||||||
~ $ ( declare -ia 'a=(2+4 [2]=2+2 [a[2]]="a[2]")' 'a+=(42 [a[4]]+=3)'; declare -p a )
|
~ $ ( declare -ia 'a=(2+4 [2]=2+2 [a[2]]="a[2]")' 'a+=(42 [a[4]]+=3)'; declare -p a )
|
||||||
declare -ai a='([0]="6" [2]="4" [4]="7" [5]="42")'
|
declare -ai a='([0]="6" [2]="4" [4]="7" [5]="42")'
|
||||||
|
|
||||||
`a[0]` is assigned to the result of `2+4`. `a[2]` gets the result of
|
`a[0]` is assigned to the result of `2+4`. `a[2]` gets the result of
|
||||||
`2+2`. The last index in the first assignment is the result of `a[2]`,
|
`2+2`. The last index in the first assignment is the result of `a[2]`,
|
||||||
@ -428,29 +428,29 @@ without eval using the aforementioned special compound assignment
|
|||||||
expansion.
|
expansion.
|
||||||
|
|
||||||
isSubset() {
|
isSubset() {
|
||||||
local -a 'xkeys=("${!'"$1"'[@]}")' 'ykeys=("${!'"$2"'[@]}")'
|
local -a 'xkeys=("${!'"$1"'[@]}")' 'ykeys=("${!'"$2"'[@]}")'
|
||||||
set -- "${@/%/[key]}"
|
set -- "${@/%/[key]}"
|
||||||
|
|
||||||
(( ${#xkeys[@]} <= ${#ykeys[@]} )) || return 1
|
(( ${#xkeys[@]} <= ${#ykeys[@]} )) || return 1
|
||||||
|
|
||||||
local key
|
local key
|
||||||
for key in "${xkeys[@]}"; do
|
for key in "${xkeys[@]}"; do
|
||||||
[[ ${!2+_} && ${!1} == ${!2} ]] || return 1
|
[[ ${!2+_} && ${!1} == ${!2} ]] || return 1
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
# "a" is a subset of "b"
|
# "a" is a subset of "b"
|
||||||
local -a 'a=({0..5})' 'b=({0..10})'
|
local -a 'a=({0..5})' 'b=({0..10})'
|
||||||
isSubset a b
|
isSubset a b
|
||||||
echo $? # true
|
echo $? # true
|
||||||
|
|
||||||
# "a" contains a key not in "b"
|
# "a" contains a key not in "b"
|
||||||
local -a 'a=([5]=5 {6..11})' 'b=({0..10})'
|
local -a 'a=([5]=5 {6..11})' 'b=({0..10})'
|
||||||
isSubset a b
|
isSubset a b
|
||||||
echo $? # false
|
echo $? # false
|
||||||
|
|
||||||
# "a" contains an element whose value != the corresponding member of "b"
|
# "a" contains an element whose value != the corresponding member of "b"
|
||||||
local -a 'a=([5]=5 6 8 9 10)' 'b=({0..10})'
|
local -a 'a=([5]=5 6 8 9 10)' 'b=({0..10})'
|
||||||
isSubset a b
|
isSubset a b
|
||||||
echo $? # false
|
echo $? # false
|
||||||
@ -465,7 +465,7 @@ dynamically calls a function whose name is resolved from the array.
|
|||||||
|
|
||||||
callFuncs() {
|
callFuncs() {
|
||||||
# Set up indirect references as positional parameters to minimize local name collisions.
|
# Set up indirect references as positional parameters to minimize local name collisions.
|
||||||
set -- "${@:1:3}" ${2+'a["$1"]' "$1"'["$2"]'}
|
set -- "${@:1:3}" ${2+'a["$1"]' "$1"'["$2"]'}
|
||||||
|
|
||||||
# The only way to test for set but null parameters is unfortunately to test each individually.
|
# The only way to test for set but null parameters is unfortunately to test each individually.
|
||||||
local x
|
local x
|
||||||
@ -477,23 +477,23 @@ dynamically calls a function whose name is resolved from the array.
|
|||||||
[foo]='([r]=f [s]=g [t]=h)'
|
[foo]='([r]=f [s]=g [t]=h)'
|
||||||
[bar]='([u]=i [v]=j [w]=k)'
|
[bar]='([u]=i [v]=j [w]=k)'
|
||||||
[baz]='([x]=l [y]=m [z]=n)'
|
[baz]='([x]=l [y]=m [z]=n)'
|
||||||
) ${4+${a["$1"]+"${1}=${!3}"}} # For example, if "$1" is "bar" then define a new array: bar=([u]=i [v]=j [w]=k)
|
) ${4+${a["$1"]+"${1}=${!3}"}} # For example, if "$1" is "bar" then define a new array: bar=([u]=i [v]=j [w]=k)
|
||||||
|
|
||||||
${4+${a["$1"]+"${!4-:}"}} # Now just lookup the new array. for inputs: "bar" "v", the function named "j" will be called, which prints "j" to stdout.
|
${4+${a["$1"]+"${!4-:}"}} # Now just lookup the new array. for inputs: "bar" "v", the function named "j" will be called, which prints "j" to stdout.
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
# Define functions named {f..n} which just print their own names.
|
# Define functions named {f..n} which just print their own names.
|
||||||
local fun='() { echo "$FUNCNAME"; }' x
|
local fun='() { echo "$FUNCNAME"; }' x
|
||||||
|
|
||||||
for x in {f..n}; do
|
for x in {f..n}; do
|
||||||
eval "${x}${fun}"
|
eval "${x}${fun}"
|
||||||
done
|
done
|
||||||
|
|
||||||
callFuncs "$@"
|
callFuncs "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|
||||||
## Bugs and Portability Considerations
|
## Bugs and Portability Considerations
|
||||||
|
|
||||||
@ -537,7 +537,7 @@ dynamically calls a function whose name is resolved from the array.
|
|||||||
` $ ksh -c 'function f { typeset -a a; a+=(foo bar baz); a+=([3]=blah [0]=bork [1]=blarg [2]=zooj); typeset -p a; }; f' # ksh93 forces appending to the array, disregarding subscripts
|
` $ ksh -c 'function f { typeset -a a; a+=(foo bar baz); a+=([3]=blah [0]=bork [1]=blarg [2]=zooj); typeset -p a; }; f' # ksh93 forces appending to the array, disregarding subscripts
|
||||||
typeset -a a=(foo bar baz '[3]=blah' '[0]=bork' '[1]=blarg' '[2]=zooj')
|
typeset -a a=(foo bar baz '[3]=blah' '[0]=bork' '[1]=blarg' '[2]=zooj')
|
||||||
$ bash -c 'function f { typeset -a a; a+=(foo bar baz); a+=(blah [0]=bork blarg zooj); typeset -p a; }; f' # Bash applies += to every individual subscript.
|
$ bash -c 'function f { typeset -a a; a+=(foo bar baz); a+=(blah [0]=bork blarg zooj); typeset -p a; }; f' # Bash applies += to every individual subscript.
|
||||||
declare -a a='([0]="foobork" [1]="barblarg" [2]="bazzooj" [3]="blah")'
|
declare -a a='([0]="foobork" [1]="barblarg" [2]="bazzooj" [3]="blah")'
|
||||||
$ mksh -c 'function f { typeset -a a; a+=(foo bar baz); a+=(blah [0]=bork blarg zooj); typeset -p a; }; f' # Mksh does like Bash, but clobbers previous values rather than appending.
|
$ mksh -c 'function f { typeset -a a; a+=(foo bar baz); a+=(blah [0]=bork blarg zooj); typeset -p a; }; f' # Mksh does like Bash, but clobbers previous values rather than appending.
|
||||||
set -A a
|
set -A a
|
||||||
typeset a[0]=bork
|
typeset a[0]=bork
|
||||||
@ -570,7 +570,7 @@ dynamically calls a function whose name is resolved from the array.
|
|||||||
typeset -a arr=(foo bar bork baz)
|
typeset -a arr=(foo bar bork baz)
|
||||||
$ ksh -c 'set -sA arr -- foo bar bork baz; typeset -p arr' # Native sorting!
|
$ ksh -c 'set -sA arr -- foo bar bork baz; typeset -p arr' # Native sorting!
|
||||||
typeset -a arr=(bar baz bork foo)
|
typeset -a arr=(bar baz bork foo)
|
||||||
$ mksh -c 'set -sA arr -- foo "[3]=bar" "[2]=baz" "[7]=bork"; typeset -p arr' # Probably a bug. I think the maintainer is aware of it.
|
$ mksh -c 'set -sA arr -- foo "[3]=bar" "[2]=baz" "[7]=bork"; typeset -p arr' # Probably a bug. I think the maintainer is aware of it.
|
||||||
set -A arr
|
set -A arr
|
||||||
typeset arr[2]=baz
|
typeset arr[2]=baz
|
||||||
typeset arr[3]=bar
|
typeset arr[3]=bar
|
||||||
@ -610,11 +610,11 @@ dynamically calls a function whose name is resolved from the array.
|
|||||||
the right of the `=` sign. This is fixed in 4.3, so that each
|
the right of the `=` sign. This is fixed in 4.3, so that each
|
||||||
subscript assignment statement is expanded following the same rules as
|
subscript assignment statement is expanded following the same rules as
|
||||||
an ordinary assignment. This also works correctly in ksh93.
|
an ordinary assignment. This also works correctly in ksh93.
|
||||||
`$ touch '[1]=a'; bash -c 'a=([1]=*); echo "${a[@]}"'
|
`$ touch '[1]=a'; bash -c 'a=([1]=*); echo "${a[@]}"'
|
||||||
[1]=a
|
[1]=a
|
||||||
` mksh has a similar but even worse problem in that the entire
|
` mksh has a similar but even worse problem in that the entire
|
||||||
subscript is considered a glob.
|
subscript is considered a glob.
|
||||||
`$ touch 1=a; mksh -c 'a=([123]=*); print -r -- "${a[@]}"'
|
`$ touch 1=a; mksh -c 'a=([123]=*); print -r -- "${a[@]}"'
|
||||||
1=a
|
1=a
|
||||||
`
|
`
|
||||||
- **Fixed in 4.3** In addition to the above globbing issue, assignments
|
- **Fixed in 4.3** In addition to the above globbing issue, assignments
|
||||||
@ -635,13 +635,13 @@ dynamically calls a function whose name is resolved from the array.
|
|||||||
`let`, `eval`, other declaration commands, and maybe more.
|
`let`, `eval`, other declaration commands, and maybe more.
|
||||||
- **Fixed in 4.3** Indirection combined with another modifier expands
|
- **Fixed in 4.3** Indirection combined with another modifier expands
|
||||||
arrays to a single word.
|
arrays to a single word.
|
||||||
`$ a=({a..c}) b=a[@]; printf '<%s> ' "${!b}"; echo; printf '<%s> ' "${!b/%/foo}"; echo
|
`$ a=({a..c}) b=a[@]; printf '<%s> ' "${!b}"; echo; printf '<%s> ' "${!b/%/foo}"; echo
|
||||||
<a> <b> <c>
|
<a> <b> <c>
|
||||||
<a b cfoo>
|
<a b cfoo>
|
||||||
`
|
`
|
||||||
- **Fixed in 4.3** Process substitutions are evaluated within array
|
- **Fixed in 4.3** Process substitutions are evaluated within array
|
||||||
indexes. Zsh and ksh don't do this in any arithmetic context.
|
indexes. Zsh and ksh don't do this in any arithmetic context.
|
||||||
`# print "moo"
|
`# print "moo"
|
||||||
dev=fd=1 _[1<(echo moo >&2)]=
|
dev=fd=1 _[1<(echo moo >&2)]=
|
||||||
|
|
||||||
# Fork bomb
|
# Fork bomb
|
||||||
|
@ -71,7 +71,7 @@ and vice versa. In this example, the commands in the if stanza will be
|
|||||||
executed if the pattern "^root:" is **not** found in `/etc/passwd`:
|
executed if the pattern "^root:" is **not** found in `/etc/passwd`:
|
||||||
|
|
||||||
if ! grep '^root:' /etc/passwd; then
|
if ! grep '^root:' /etc/passwd; then
|
||||||
echo "No root user defined... eh?"
|
echo "No root user defined... eh?"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
Yes, this is also a pipeline (although there is no pipe!), because the
|
Yes, this is also a pipeline (although there is no pipe!), because the
|
||||||
@ -179,7 +179,7 @@ The definition is easy (one of many possibilities):
|
|||||||
which is usually used with the `{...; }` compound command, and thus
|
which is usually used with the `{...; }` compound command, and thus
|
||||||
looks like:
|
looks like:
|
||||||
|
|
||||||
print_help() { echo "Sorry, no help available"; }
|
print_help() { echo "Sorry, no help available"; }
|
||||||
|
|
||||||
As above, a function definition can have any [compound
|
As above, a function definition can have any [compound
|
||||||
command](basicgrammar#compound_commands) as a body. Structures like
|
command](basicgrammar#compound_commands) as a body. Structures like
|
||||||
@ -223,11 +223,11 @@ like this:
|
|||||||
mycmd()
|
mycmd()
|
||||||
{
|
{
|
||||||
# this $1 belongs to the function!
|
# this $1 belongs to the function!
|
||||||
find / -iname "$1"
|
find / -iname "$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
# this $1 belongs the script itself!
|
# this $1 belongs the script itself!
|
||||||
mycmd "$1" # Execute command immediately after defining function
|
mycmd "$1" # Execute command immediately after defining function
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
|
|
||||||
@ -239,7 +239,7 @@ variables. Variables with the content "*() ....*".
|
|||||||
Something similar to the following works without "officially" declaring
|
Something similar to the following works without "officially" declaring
|
||||||
a function:
|
a function:
|
||||||
|
|
||||||
$ export testfn="() { echo test; }"
|
$ export testfn="() { echo test; }"
|
||||||
$ bash -c testfn
|
$ bash -c testfn
|
||||||
test
|
test
|
||||||
$
|
$
|
||||||
@ -284,7 +284,7 @@ FIXME more...
|
|||||||
|
|
||||||
<u>A (very) simple command</u>
|
<u>A (very) simple command</u>
|
||||||
|
|
||||||
echo "Hello world..."
|
echo "Hello world..."
|
||||||
|
|
||||||
<u>All of the following are simple commands</u>
|
<u>All of the following are simple commands</u>
|
||||||
|
|
||||||
@ -292,7 +292,7 @@ FIXME more...
|
|||||||
|
|
||||||
>tmpfile
|
>tmpfile
|
||||||
|
|
||||||
{x}<"$x" _=${x=<(echo moo)} <&0$(cat <&"$x" >&2)
|
{x}<"$x" _=${x=<(echo moo)} <&0$(cat <&"$x" >&2)
|
||||||
|
|
||||||
------------------------------------------------------------------------
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ current value.
|
|||||||
<!-- -->
|
<!-- -->
|
||||||
|
|
||||||
for ((x = 0 ; x <= 100 ; x++)); do
|
for ((x = 0 ; x <= 100 ; x++)); do
|
||||||
echo "Counter: $x"
|
echo "Counter: $x"
|
||||||
done
|
done
|
||||||
|
|
||||||
### Stepping counter
|
### Stepping counter
|
||||||
@ -124,7 +124,7 @@ above), but the **change** that is made is a `x += 10`. That means, it
|
|||||||
will count from 0 to 100, but with a **step of 10**.
|
will count from 0 to 100, but with a **step of 10**.
|
||||||
|
|
||||||
for ((x = 0 ; x <= 100 ; x += 10)); do
|
for ((x = 0 ; x <= 100 ; x += 10)); do
|
||||||
echo "Counter: $x"
|
echo "Counter: $x"
|
||||||
done
|
done
|
||||||
|
|
||||||
### Bits analyzer
|
### Bits analyzer
|
||||||
@ -148,15 +148,15 @@ bits).
|
|||||||
function main {
|
function main {
|
||||||
[[ $1 == +([0-9]) ]] || return
|
[[ $1 == +([0-9]) ]] || return
|
||||||
typeset result
|
typeset result
|
||||||
if (( $(ksh -c 'printf %..2d $1' _ "$1") == ( result = $(toBin "$1") ) )); then
|
if (( $(ksh -c 'printf %..2d $1' _ "$1") == ( result = $(toBin "$1") ) )); then
|
||||||
printf '%s is %s in base 2!\n' "$1" "$result"
|
printf '%s is %s in base 2!\n' "$1" "$result"
|
||||||
else
|
else
|
||||||
echo 'Oops, something went wrong with our calculation.' >&2
|
echo 'Oops, something went wrong with our calculation.' >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
main "${1:-123}"
|
main "${1:-123}"
|
||||||
|
|
||||||
# vim: set fenc=utf-8 ff=unix ft=sh :
|
# vim: set fenc=utf-8 ff=unix ft=sh :
|
||||||
|
|
||||||
@ -191,7 +191,7 @@ demonstrating more complicated arithmetic expressions with multiple
|
|||||||
variables.
|
variables.
|
||||||
|
|
||||||
for (( incr = 1, n=0, times = ${2:-4}, step = ${1:-5}; (n += incr) % step || (incr *= -1, --times);)); do
|
for (( incr = 1, n=0, times = ${2:-4}, step = ${1:-5}; (n += incr) % step || (incr *= -1, --times);)); do
|
||||||
printf '%*s\n' "$((n+1))" "$n"
|
printf '%*s\n' "$((n+1))" "$n"
|
||||||
done
|
done
|
||||||
|
|
||||||
\<code\> ~ \$ bash \<(xclip -o) 1
|
\<code\> ~ \$ bash \<(xclip -o) 1
|
||||||
|
@ -36,7 +36,7 @@ expansion*; *arithmetic*, *command* and *process substitution*; and
|
|||||||
*quote removal*. **No word splitting, brace, or pathname expansion is
|
*quote removal*. **No word splitting, brace, or pathname expansion is
|
||||||
done**, which means you can leave expansions unquoted without problems:
|
done**, which means you can leave expansions unquoted without problems:
|
||||||
|
|
||||||
var="test word"
|
var="test word"
|
||||||
|
|
||||||
case $var in
|
case $var in
|
||||||
...
|
...
|
||||||
@ -78,7 +78,7 @@ Another one of my stupid examples...
|
|||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Unknown fruit - sure it isn't toxic?"
|
echo "Unknown fruit - sure it isn't toxic?"
|
||||||
esac
|
esac
|
||||||
|
|
||||||
Here's a practical example showing a common pattern involving a `case`
|
Here's a practical example showing a common pattern involving a `case`
|
||||||
@ -95,13 +95,13 @@ function clk {
|
|||||||
|
|
||||||
case $1 in
|
case $1 in
|
||||||
low|high|default)
|
low|high|default)
|
||||||
printf '%s\n' "temp: $(<${base}/hwmon/hwmon0/temp1_input)C" "old profile: $(<${base}/power_profile)"
|
printf '%s\n' "temp: $(<${base}/hwmon/hwmon0/temp1_input)C" "old profile: $(<${base}/power_profile)"
|
||||||
echo "$1" >${base}/power_profile
|
echo "$1" >${base}/power_profile
|
||||||
echo "new profile: $(<${base}/power_profile)"
|
echo "new profile: $(<${base}/power_profile)"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Usage: $FUNCNAME [ low | high | default ]"
|
echo "Usage: $FUNCNAME [ low | high | default ]"
|
||||||
printf '%s\n' "temp: $(<${base}/hwmon/hwmon0/temp1_input)C" "current profile: $(<${base}/power_profile)"
|
printf '%s\n' "temp: $(<${base}/hwmon/hwmon0/temp1_input)C" "current profile: $(<${base}/power_profile)"
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -113,25 +113,25 @@ between blocks using `;&`, and the non-short-circuiting `;;&` operator:
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
f() {
|
f() {
|
||||||
local -a "$@"
|
local -a "$@"
|
||||||
local x
|
local x
|
||||||
|
|
||||||
for x; do
|
for x; do
|
||||||
case $x in
|
case $x in
|
||||||
$1)
|
$1)
|
||||||
local "$x"'+=(1)' ;;&
|
local "$x"'+=(1)' ;;&
|
||||||
$2)
|
$2)
|
||||||
local "$x"'+=(2)' ;&
|
local "$x"'+=(2)' ;&
|
||||||
$3)
|
$3)
|
||||||
local "$x"'+=(3)' ;;
|
local "$x"'+=(3)' ;;
|
||||||
$1|$2)
|
$1|$2)
|
||||||
local "$x"'+=(4)'
|
local "$x"'+=(4)'
|
||||||
esac
|
esac
|
||||||
IFS=, local -a "$x"'=("${x}: ${'"$x"'[*]}")'
|
IFS=, local -a "$x"'=("${x}: ${'"$x"'[*]}")'
|
||||||
done
|
done
|
||||||
|
|
||||||
for x; do
|
for x; do
|
||||||
echo "${!x}"
|
echo "${!x}"
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,8 +71,8 @@ With some array syntax (see [arrays](/syntax/arrays)) you can easily
|
|||||||
mass-expanding all elements):
|
mass-expanding all elements):
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
for element in "${myarray[@]}"; do
|
for element in "${myarray[@]}"; do
|
||||||
echo "Element: $element"
|
echo "Element: $element"
|
||||||
done
|
done
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -80,8 +80,8 @@ Another way is to mass-expand all used indexes and access the array by
|
|||||||
index:
|
index:
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
for index in "${!myarray[@]}"; do
|
for index in "${!myarray[@]}"; do
|
||||||
echo "Element[$index]: ${myarray[$index]}"
|
echo "Element[$index]: ${myarray[$index]}"
|
||||||
done
|
done
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -95,7 +95,7 @@ arguments to a command will be interpreted and parsed, and finally used:
|
|||||||
argtest() {
|
argtest() {
|
||||||
n=1
|
n=1
|
||||||
for arg; do
|
for arg; do
|
||||||
echo "Argument $((n++)): \"$arg\""
|
echo "Argument $((n++)): \"$arg\""
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -108,16 +108,16 @@ filenames in a directory:
|
|||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
for fn in *; do
|
for fn in *; do
|
||||||
if [ -h "$fn" ]; then
|
if [ -h "$fn" ]; then
|
||||||
echo -n "Symlink: "
|
echo -n "Symlink: "
|
||||||
elif [ -d "$fn" ]; then
|
elif [ -d "$fn" ]; then
|
||||||
echo -n "Dir: "
|
echo -n "Dir: "
|
||||||
elif [ -f "$fn" ]; then
|
elif [ -f "$fn" ]; then
|
||||||
echo -n "File: "
|
echo -n "File: "
|
||||||
else
|
else
|
||||||
echo -n "Unknown: "
|
echo -n "Unknown: "
|
||||||
fi
|
fi
|
||||||
echo "$fn"
|
echo "$fn"
|
||||||
done
|
done
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -22,10 +22,10 @@ status (exit code)](/scripting/basics#exit_codes) of the list.
|
|||||||
The input and output **filedescriptors** are cumulative:
|
The input and output **filedescriptors** are cumulative:
|
||||||
|
|
||||||
{
|
{
|
||||||
echo "PASSWD follows"
|
echo "PASSWD follows"
|
||||||
cat /etc/passwd
|
cat /etc/passwd
|
||||||
echo
|
echo
|
||||||
echo "GROUPS follows"
|
echo "GROUPS follows"
|
||||||
cat /etc/group
|
cat /etc/group
|
||||||
} >output.txt
|
} >output.txt
|
||||||
|
|
||||||
@ -34,10 +34,10 @@ definition](/syntax/basicgrammar#shell_function_definitions), though not
|
|||||||
the only compound command that's valid there:
|
the only compound command that's valid there:
|
||||||
|
|
||||||
print_help() {
|
print_help() {
|
||||||
echo "Options:"
|
echo "Options:"
|
||||||
echo "-h This help text"
|
echo "-h This help text"
|
||||||
echo "-f FILE Use config file FILE"
|
echo "-f FILE Use config file FILE"
|
||||||
echo "-u USER Run as user USER"
|
echo "-u USER Run as user USER"
|
||||||
}
|
}
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
@ -46,10 +46,10 @@ the only compound command that's valid there:
|
|||||||
|
|
||||||
try_catch() {
|
try_catch() {
|
||||||
{ # Try-block:
|
{ # Try-block:
|
||||||
eval "$@"
|
eval "$@"
|
||||||
} ||
|
} ||
|
||||||
{ # Catch-block:
|
{ # Catch-block:
|
||||||
echo "An error occurred"
|
echo "An error occurred"
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,9 +15,9 @@ etc...) are reflected in the "main shell".
|
|||||||
Execute a command in a different directory.
|
Execute a command in a different directory.
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
echo "$PWD"
|
echo "$PWD"
|
||||||
( cd /usr; echo "$PWD" )
|
( cd /usr; echo "$PWD" )
|
||||||
echo "$PWD" # Still in the original directory.
|
echo "$PWD" # Still in the original directory.
|
||||||
```
|
```
|
||||||
|
|
||||||
## Portability considerations
|
## Portability considerations
|
||||||
|
@ -51,15 +51,15 @@ commands of the condition that succeeded.
|
|||||||
**Check if a specific user exists in /etc/passwd :-)**
|
**Check if a specific user exists in /etc/passwd :-)**
|
||||||
|
|
||||||
if grep ^myuser: /etc/passwd >/dev/null 2>&1; then
|
if grep ^myuser: /etc/passwd >/dev/null 2>&1; then
|
||||||
echo "Yes, it seems I'm real"
|
echo "Yes, it seems I'm real"
|
||||||
else
|
else
|
||||||
echo "Uh - am I a ghost?"
|
echo "Uh - am I a ghost?"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
**Mount with check**
|
**Mount with check**
|
||||||
|
|
||||||
if ! mount /mnt/backup >/dev/null 2>&1; then
|
if ! mount /mnt/backup >/dev/null 2>&1; then
|
||||||
echo "FATAL: backup mount failed" >&2
|
echo "FATAL: backup mount failed" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ commands of the condition that succeeded.
|
|||||||
|
|
||||||
It's perfectly valid to do:
|
It's perfectly valid to do:
|
||||||
|
|
||||||
if echo "I'm testing!"; [ -e /some/file ]; then
|
if echo "I'm testing!"; [ -e /some/file ]; then
|
||||||
...
|
...
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -80,8 +80,8 @@ the very last command executed in the condition-list (here: The
|
|||||||
A complete pipe can also be used as condition. It's very similar to the
|
A complete pipe can also be used as condition. It's very similar to the
|
||||||
example above (multiple commands):
|
example above (multiple commands):
|
||||||
|
|
||||||
if echo "Hello world!" | grep -i hello >/dev/null 2>&1; then
|
if echo "Hello world!" | grep -i hello >/dev/null 2>&1; then
|
||||||
echo "You just said 'hello', yeah?"
|
echo "You just said 'hello', yeah?"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
## Portability considerations
|
## Portability considerations
|
||||||
|
@ -18,8 +18,8 @@ function printSum {
|
|||||||
typeset -A args
|
typeset -A args
|
||||||
typeset name
|
typeset name
|
||||||
for name in first second; do
|
for name in first second; do
|
||||||
[[ -t 0 ]] && printf 'Enter %s positive integer: ' "$name" >&2
|
[[ -t 0 ]] && printf 'Enter %s positive integer: ' "$name" >&2
|
||||||
read -r ${BASH_VERSION+-e} "args[$name]"
|
read -r ${BASH_VERSION+-e} "args[$name]"
|
||||||
[[ ${args[$name]} == +([[:digit:]]) ]] || return 1 # Validation is extremely important whenever user input is used in arithmetic.
|
[[ ${args[$name]} == +([[:digit:]]) ]] || return 1 # Validation is extremely important whenever user input is used in arithmetic.
|
||||||
done
|
done
|
||||||
printf 'The sum is %d.' $((${args[first]} + ${args[second]}))
|
printf 'The sum is %d.' $((${args[first]} + ${args[second]}))
|
||||||
@ -49,7 +49,7 @@ x=1
|
|||||||
|
|
||||||
echo $((x)) # Good.
|
echo $((x)) # Good.
|
||||||
echo $(($x)) # Ok. Avoid expansions within arithmetic. Use variables directly.
|
echo $(($x)) # Ok. Avoid expansions within arithmetic. Use variables directly.
|
||||||
echo $(("$x")) # Error. There is no quote-removal in arithmetic contexts. It expands to $(("1")), which is an invalid arithmetic expression.
|
echo $(("$x")) # Error. There is no quote-removal in arithmetic contexts. It expands to $(("1")), which is an invalid arithmetic expression.
|
||||||
echo $((x[0])) # Good.
|
echo $((x[0])) # Good.
|
||||||
echo $((${x[0]})) # Ok. Nested expansion again.
|
echo $((${x[0]})) # Ok. Nested expansion again.
|
||||||
echo $((${x[$((${x[!$x]}-$x))]})) # Same as above but more ridiculous.
|
echo $((${x[$((${x[!$x]}-$x))]})) # Same as above but more ridiculous.
|
||||||
|
@ -41,7 +41,7 @@ requires that the entire command be properly escaped to avoid unexpected
|
|||||||
expansions. If the sequence expansion is to be assigned to an array,
|
expansions. If the sequence expansion is to be assigned to an array,
|
||||||
another method is possible using [declaration
|
another method is possible using [declaration
|
||||||
commands](/commands/builtin/declare):
|
commands](/commands/builtin/declare):
|
||||||
`declare -a 'pics=(img{'"$a..$b"'}.png)'; mv "${pics[@]}" ../imgs`
|
`declare -a 'pics=(img{'"$a..$b"'}.png)'; mv "${pics[@]}" ../imgs`
|
||||||
This is significantly safer, but one must still be careful to control
|
This is significantly safer, but one must still be careful to control
|
||||||
the values of \$a and \$b. Both the exact quoting, and explicitly
|
the values of \$a and \$b. Both the exact quoting, and explicitly
|
||||||
including "-a" are important.
|
including "-a" are important.
|
||||||
@ -181,18 +181,18 @@ for.
|
|||||||
|
|
||||||
<!-- -->
|
<!-- -->
|
||||||
|
|
||||||
for i in 0{1..9} 10; do printf "%s\n" "$i";done
|
for i in 0{1..9} 10; do printf "%s\n" "$i";done
|
||||||
|
|
||||||
If you need to create words with the number embedded, you can use nested
|
If you need to create words with the number embedded, you can use nested
|
||||||
brace:
|
brace:
|
||||||
|
|
||||||
printf "%s\n" img{00{1..9},0{10..99},{100..999}}.png
|
printf "%s\n" img{00{1..9},0{10..99},{100..999}}.png
|
||||||
|
|
||||||
- Formatting the numbers with printf:
|
- Formatting the numbers with printf:
|
||||||
|
|
||||||
<!-- -->
|
<!-- -->
|
||||||
|
|
||||||
echo $(printf "img%02d.png " {1..99})
|
echo $(printf "img%02d.png " {1..99})
|
||||||
|
|
||||||
See the [text below](#news_in_bash_4.0) for a new Bash 4 method.
|
See the [text below](#news_in_bash_4.0) for a new Bash 4 method.
|
||||||
|
|
||||||
@ -218,18 +218,18 @@ any number of arguments.
|
|||||||
function braceify {
|
function braceify {
|
||||||
[[ $1 == +([[:digit:]]) ]] || return
|
[[ $1 == +([[:digit:]]) ]] || return
|
||||||
typeset -a a
|
typeset -a a
|
||||||
read -ra a < <(factor "$1")
|
read -ra a < <(factor "$1")
|
||||||
eval "echo $(printf '{$(printf ,%%.s {1..%s})}' "${a[@]:1}")"
|
eval "echo $(printf '{$(printf ,%%.s {1..%s})}' "${a[@]:1}")"
|
||||||
}
|
}
|
||||||
|
|
||||||
printf 'eval printf "$arg"%s' "$(braceify 1000000)"
|
printf 'eval printf "$arg"%s' "$(braceify 1000000)"
|
||||||
|
|
||||||
"Braceify" generates the expansion code itself. In this example we
|
"Braceify" generates the expansion code itself. In this example we
|
||||||
inject that output into a template which displays the most terse brace
|
inject that output into a template which displays the most terse brace
|
||||||
expansion code that would expand `"$arg"` 1,000,000 times if evaluated.
|
expansion code that would expand `"$arg"` 1,000,000 times if evaluated.
|
||||||
In this case, the output is:
|
In this case, the output is:
|
||||||
|
|
||||||
eval printf "$arg"{,,}{,,}{,,}{,,}{,,}{,,}{,,,,,}{,,,,,}{,,,,,}{,,,,,}{,,,,,}{,,,,,}
|
eval printf "$arg"{,,}{,,}{,,}{,,}{,,}{,,}{,,,,,}{,,,,,}{,,,,,}{,,,,,}{,,,,,}{,,,,,}
|
||||||
|
|
||||||
\</div\>
|
\</div\>
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ substitution, while every `$()`-construct opens an own, subsequent
|
|||||||
parsing step. Everything inside `$()` is interpreted as if written
|
parsing step. Everything inside `$()` is interpreted as if written
|
||||||
normal on a commandline. No special escaping of **nothing** is needed:
|
normal on a commandline. No special escaping of **nothing** is needed:
|
||||||
|
|
||||||
echo "$(echo "$(ls)")" # nested double-quotes - no problem
|
echo "$(echo "$(ls)")" # nested double-quotes - no problem
|
||||||
|
|
||||||
**<u>Constructs you should avoid</u>**
|
**<u>Constructs you should avoid</u>**
|
||||||
|
|
||||||
@ -91,10 +91,10 @@ substitution step and echo prints "ls" and ")":
|
|||||||
It seems that every closing ")" confuses this construct. Also a (very
|
It seems that every closing ")" confuses this construct. Also a (very
|
||||||
uncommon ;-)) construct like:
|
uncommon ;-)) construct like:
|
||||||
|
|
||||||
echo $(read VAR; case "$var" in foo) blah ;; esac) # spits out some error, when it sees the ";;"
|
echo $(read VAR; case "$var" in foo) blah ;; esac) # spits out some error, when it sees the ";;"
|
||||||
|
|
||||||
# fixes it:
|
# fixes it:
|
||||||
echo $(read VAR; case "$var" in (foo) blah ;; esac) # will work, but just let it be, please ;-)
|
echo $(read VAR; case "$var" in (foo) blah ;; esac) # will work, but just let it be, please ;-)
|
||||||
|
|
||||||
**<u>Conclusion:</u>**
|
**<u>Conclusion:</u>**
|
||||||
|
|
||||||
@ -110,19 +110,19 @@ In general, the `$()` should be the preferred method:
|
|||||||
|
|
||||||
**To get the date:**
|
**To get the date:**
|
||||||
|
|
||||||
DATE="$(date)"
|
DATE="$(date)"
|
||||||
|
|
||||||
**To copy a file and get `cp` error output:**
|
**To copy a file and get `cp` error output:**
|
||||||
|
|
||||||
COPY_OUTPUT="$(cp file.txt /some/where 2>&1)"
|
COPY_OUTPUT="$(cp file.txt /some/where 2>&1)"
|
||||||
|
|
||||||
Attention: Here, you need to redirect `cp` `STDERR` to its `STDOUT`
|
Attention: Here, you need to redirect `cp` `STDERR` to its `STDOUT`
|
||||||
target, because command substitution only catches `STDOUT`!
|
target, because command substitution only catches `STDOUT`!
|
||||||
|
|
||||||
**Catch stdout and preserve trailing newlines:**
|
**Catch stdout and preserve trailing newlines:**
|
||||||
|
|
||||||
var=$(echo -n $'\n'); echo -n "$var"; # $var == ""
|
var=$(echo -n $'\n'); echo -n "$var"; # $var == ""
|
||||||
var=$(echo -n $'\n'; echo -n x); var="${var%x}"; echo -n "$var" # $var == "\n"
|
var=$(echo -n $'\n'; echo -n x); var="${var%x}"; echo -n "$var" # $var == "\n"
|
||||||
|
|
||||||
This adds "x" to the output, which prevents the trailing newlines of the
|
This adds "x" to the output, which prevents the trailing newlines of the
|
||||||
previous commands' output from being deleted by \$().
|
previous commands' output from being deleted by \$().
|
||||||
|
@ -8,7 +8,7 @@ A called program will never see the glob itself; it will only see the
|
|||||||
expanded filenames as its arguments (here, all filenames matching
|
expanded filenames as its arguments (here, all filenames matching
|
||||||
`*.log`):
|
`*.log`):
|
||||||
|
|
||||||
grep "changes:" *.log
|
grep "changes:" *.log
|
||||||
|
|
||||||
The base syntax for the pathname expansion is the [pattern
|
The base syntax for the pathname expansion is the [pattern
|
||||||
matching](/syntax/pattern) syntax. The pattern you describe is matched
|
matching](/syntax/pattern) syntax. The pattern you describe is matched
|
||||||
@ -59,7 +59,7 @@ Normally, when no glob specified matches an existing filename, no
|
|||||||
pathname expansion is performed, and the globs are <u>**not**</u>
|
pathname expansion is performed, and the globs are <u>**not**</u>
|
||||||
removed:
|
removed:
|
||||||
|
|
||||||
$ echo "Textfiles here:" *.txt
|
$ echo "Textfiles here:" *.txt
|
||||||
Textfiles here: *.txt
|
Textfiles here: *.txt
|
||||||
|
|
||||||
In this example, no files matched the pattern, so the glob was left
|
In this example, no files matched the pattern, so the glob was left
|
||||||
@ -69,9 +69,9 @@ This can be very annoying, for example when you drive a
|
|||||||
[for-loop](/syntax/ccmd/classic_for) using the pathname expansion:
|
[for-loop](/syntax/ccmd/classic_for) using the pathname expansion:
|
||||||
|
|
||||||
for filename in *.txt; do
|
for filename in *.txt; do
|
||||||
echo "=== BEGIN: $filename ==="
|
echo "=== BEGIN: $filename ==="
|
||||||
cat "$filename"
|
cat "$filename"
|
||||||
echo "=== END: $filename ==="
|
echo "=== END: $filename ==="
|
||||||
done
|
done
|
||||||
|
|
||||||
When no file name matches the glob, the loop will not only output stupid
|
When no file name matches the glob, the loop will not only output stupid
|
||||||
@ -85,7 +85,7 @@ even one iteration will be done. It just won't run.
|
|||||||
So in our first example:
|
So in our first example:
|
||||||
|
|
||||||
$ shopt -s nullglob
|
$ shopt -s nullglob
|
||||||
$ echo "Textfiles here:" *.txt
|
$ echo "Textfiles here:" *.txt
|
||||||
Textfiles here:
|
Textfiles here:
|
||||||
|
|
||||||
and the glob is gone.
|
and the glob is gone.
|
||||||
|
@ -11,8 +11,8 @@ a changed command line: the elements are said to be **expanded** to or
|
|||||||
|
|
||||||
The most simple example of this behaviour is a referenced variable:
|
The most simple example of this behaviour is a referenced variable:
|
||||||
|
|
||||||
mystring="Hello world"
|
mystring="Hello world"
|
||||||
echo "$mystring"
|
echo "$mystring"
|
||||||
|
|
||||||
The `echo` program definitely doesn't care about what a shell variable
|
The `echo` program definitely doesn't care about what a shell variable
|
||||||
is. It is Bash's job to deal with the variable. Bash **expands** the
|
is. It is Bash's job to deal with the variable. Bash **expands** the
|
||||||
|
@ -71,7 +71,7 @@ file `/dev/fd/63`.
|
|||||||
Consider the following:
|
Consider the following:
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
diff <(ls "$first_directory") <(ls "$second_directory")
|
diff <(ls "$first_directory") <(ls "$second_directory")
|
||||||
```
|
```
|
||||||
|
|
||||||
This will compare the contents of each directory. In this command, each
|
This will compare the contents of each directory. In this command, each
|
||||||
@ -102,7 +102,7 @@ find /etc -print0 | while IFS= read -rd '' _; do
|
|||||||
((counter++))
|
((counter++))
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "$counter files" # prints "0 files"
|
echo "$counter files" # prints "0 files"
|
||||||
```
|
```
|
||||||
|
|
||||||
Due to the pipe, the `while read; do ... done` part is executed in a
|
Due to the pipe, the `while read; do ... done` part is executed in a
|
||||||
@ -121,7 +121,7 @@ while IFS= read -rN1 _; do
|
|||||||
((counter++))
|
((counter++))
|
||||||
done < <(find /etc -printf ' ')
|
done < <(find /etc -printf ' ')
|
||||||
|
|
||||||
echo "$counter files"
|
echo "$counter files"
|
||||||
```
|
```
|
||||||
|
|
||||||
This is the normal input file redirection `< FILE`, just that the `FILE`
|
This is the normal input file redirection `< FILE`, just that the `FILE`
|
||||||
@ -143,7 +143,7 @@ resemble "passable" objects. This results in converting the output of
|
|||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
f() {
|
f() {
|
||||||
cat "$1" >"$x"
|
cat "$1" >"$x"
|
||||||
}
|
}
|
||||||
|
|
||||||
x=>(tr '[:lower:]' '[:upper:]') f <(echo 'hi there')
|
x=>(tr '[:lower:]' '[:upper:]') f <(echo 'hi there')
|
||||||
@ -167,7 +167,7 @@ See the above section on [\#scope](#scope)
|
|||||||
other arithmetic contexts. Ksh and Zsh do not. (Possible Bug)
|
other arithmetic contexts. Ksh and Zsh do not. (Possible Bug)
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
# print "moo"
|
# print "moo"
|
||||||
dev=fd=1 _[1<(echo moo >&2)]=
|
dev=fd=1 _[1<(echo moo >&2)]=
|
||||||
# fork bomb
|
# fork bomb
|
||||||
${dev[${dev='dev[1>(${dev[dev]})]'}]}
|
${dev[${dev='dev[1>(${dev[dev]})]'}]}
|
||||||
|
@ -49,7 +49,7 @@ This way you can correctly use the tilde expansion in your
|
|||||||
|
|
||||||
**Spaces in the referenced pathes?** A construct like...
|
**Spaces in the referenced pathes?** A construct like...
|
||||||
|
|
||||||
~/"my directory"
|
~/"my directory"
|
||||||
|
|
||||||
...is perfectly valid and works!
|
...is perfectly valid and works!
|
||||||
|
|
||||||
@ -83,11 +83,11 @@ at least regarding tilde expansion.
|
|||||||
This expands to the value of the [PWD](/syntax/shellvars#PWD) variable,
|
This expands to the value of the [PWD](/syntax/shellvars#PWD) variable,
|
||||||
which holds the currect working directory:
|
which holds the currect working directory:
|
||||||
|
|
||||||
echo "CWD is $PWD"
|
echo "CWD is $PWD"
|
||||||
|
|
||||||
is equivalent to (note it **must** be a separate word!):
|
is equivalent to (note it **must** be a separate word!):
|
||||||
|
|
||||||
echo "CWD is" ~+
|
echo "CWD is" ~+
|
||||||
|
|
||||||
## Previous working directory
|
## Previous working directory
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ The behavior regarding the variable assignment errors can be tested:
|
|||||||
echo PRE
|
echo PRE
|
||||||
|
|
||||||
# The following is an assignment error!
|
# The following is an assignment error!
|
||||||
# The "echo TEST" won't be executed, since the { ...; } is terminated
|
# The "echo TEST" won't be executed, since the { ...; } is terminated
|
||||||
{ foo=$((8#9)); echo TEST; }
|
{ foo=$((8#9)); echo TEST; }
|
||||||
|
|
||||||
echo POST
|
echo POST
|
||||||
|
@ -35,13 +35,13 @@ Some examples:
|
|||||||
# redirecting stderr in the pipe
|
# redirecting stderr in the pipe
|
||||||
$ coproc { ls thisfiledoesntexist; read; } 2>&1
|
$ coproc { ls thisfiledoesntexist; read; } 2>&1
|
||||||
[2] 23084
|
[2] 23084
|
||||||
$ IFS= read -ru ${COPROC[0]} x; printf '%s\n' "$x"
|
$ IFS= read -ru ${COPROC[0]} x; printf '%s\n' "$x"
|
||||||
ls: cannot access thisfiledoesntexist: No such file or directory
|
ls: cannot access thisfiledoesntexist: No such file or directory
|
||||||
```
|
```
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
#let the output of the coprocess go to stdout
|
#let the output of the coprocess go to stdout
|
||||||
$ { coproc mycoproc { awk '{print "foo" $0;fflush()}'; } >&3; } 3>&1
|
$ { coproc mycoproc { awk '{print "foo" $0;fflush()}'; } >&3; } 3>&1
|
||||||
[2] 23092
|
[2] 23092
|
||||||
$ echo bar >&${mycoproc[1]}
|
$ echo bar >&${mycoproc[1]}
|
||||||
$ foobar
|
$ foobar
|
||||||
@ -64,7 +64,7 @@ In Ksh you would do:
|
|||||||
``` bash
|
``` bash
|
||||||
# ksh93 or mksh/pdksh derivatives
|
# ksh93 or mksh/pdksh derivatives
|
||||||
ls |& # start a coprocess
|
ls |& # start a coprocess
|
||||||
while IFS= read -rp file; do print -r -- "$file"; done # read its output
|
while IFS= read -rp file; do print -r -- "$file"; done # read its output
|
||||||
```
|
```
|
||||||
|
|
||||||
In bash:
|
In bash:
|
||||||
@ -73,7 +73,7 @@ In bash:
|
|||||||
#DOESN'T WORK
|
#DOESN'T WORK
|
||||||
$ coproc ls
|
$ coproc ls
|
||||||
[1] 23232
|
[1] 23232
|
||||||
$ while IFS= read -ru ${COPROC[0]} line; do printf '%s\n' "$line"; done
|
$ while IFS= read -ru ${COPROC[0]} line; do printf '%s\n' "$line"; done
|
||||||
bash: read: line: invalid file descriptor specification
|
bash: read: line: invalid file descriptor specification
|
||||||
[1]+ Done coproc COPROC ls
|
[1]+ Done coproc COPROC ls
|
||||||
```
|
```
|
||||||
@ -95,7 +95,7 @@ with `sed`:
|
|||||||
$ coproc sed s/^/foo/
|
$ coproc sed s/^/foo/
|
||||||
[1] 22981
|
[1] 22981
|
||||||
$ echo bar >&${COPROC[1]}
|
$ echo bar >&${COPROC[1]}
|
||||||
$ read -t 3 -ru ${COPROC[0]} _; (( $? > 127 )) && echo "nothing read"
|
$ read -t 3 -ru ${COPROC[0]} _; (( $? > 127 )) && echo "nothing read"
|
||||||
nothing read
|
nothing read
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -116,9 +116,9 @@ continuously read the output of a coprocess and `echo` the result:
|
|||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
#NOT WORKING
|
#NOT WORKING
|
||||||
$ coproc awk '{print "foo" $0;fflush()}'
|
$ coproc awk '{print "foo" $0;fflush()}'
|
||||||
[2] 23100
|
[2] 23100
|
||||||
$ while IFS= read -ru ${COPROC[0]} x; do printf '%s\n' "$x"; done &
|
$ while IFS= read -ru ${COPROC[0]} x; do printf '%s\n' "$x"; done &
|
||||||
[3] 23104
|
[3] 23104
|
||||||
bash: line 243: read: 61: invalid file descriptor: Bad file descriptor
|
bash: line 243: read: 61: invalid file descriptor: Bad file descriptor
|
||||||
```
|
```
|
||||||
@ -132,10 +132,10 @@ A possible workaround:
|
|||||||
#WARNING: for illustration purpose ONLY
|
#WARNING: for illustration purpose ONLY
|
||||||
# this is not the way to make the coprocess print its output
|
# this is not the way to make the coprocess print its output
|
||||||
# to stdout, see the redirections above.
|
# to stdout, see the redirections above.
|
||||||
$ coproc awk '{print "foo" $0;fflush()}'
|
$ coproc awk '{print "foo" $0;fflush()}'
|
||||||
[2] 23109
|
[2] 23109
|
||||||
$ exec 3<&${COPROC[0]}
|
$ exec 3<&${COPROC[0]}
|
||||||
$ while IFS= read -ru 3 x; do printf '%s\n' "$x"; done &
|
$ while IFS= read -ru 3 x; do printf '%s\n' "$x"; done &
|
||||||
[3] 23110
|
[3] 23110
|
||||||
$ echo bar >&${COPROC[1]}
|
$ echo bar >&${COPROC[1]}
|
||||||
$ foobar
|
$ foobar
|
||||||
@ -152,7 +152,7 @@ assigns FDs to a default array named `COPROC` if no `NAME` is supplied.
|
|||||||
Here's an example:
|
Here's an example:
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
$ coproc awk '{print "foo" $0;fflush()}'
|
$ coproc awk '{print "foo" $0;fflush()}'
|
||||||
[1] 22978
|
[1] 22978
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -167,7 +167,7 @@ $ echo bar >&${COPROC[1]}
|
|||||||
And then read its output:
|
And then read its output:
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
$ IFS= read -ru ${COPROC[0]} x; printf '%s\n' "$x"
|
$ IFS= read -ru ${COPROC[0]} x; printf '%s\n' "$x"
|
||||||
foobar
|
foobar
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -175,7 +175,7 @@ When we don't need our command anymore, we can kill it via its pid:
|
|||||||
|
|
||||||
$ kill $COPROC_PID
|
$ kill $COPROC_PID
|
||||||
$
|
$
|
||||||
[1]+ Terminated coproc COPROC awk '{print "foo" $0;fflush()}'
|
[1]+ Terminated coproc COPROC awk '{print "foo" $0;fflush()}'
|
||||||
|
|
||||||
### Named Coprocess
|
### Named Coprocess
|
||||||
|
|
||||||
@ -184,14 +184,14 @@ when defining a function), and the resulting FDs will be assigned to the
|
|||||||
indexed array `NAME` we supply instead.
|
indexed array `NAME` we supply instead.
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
$ coproc mycoproc { awk '{print "foo" $0;fflush()}' ;}
|
$ coproc mycoproc { awk '{print "foo" $0;fflush()}' ;}
|
||||||
[1] 23058
|
[1] 23058
|
||||||
$ echo bar >&${mycoproc[1]}
|
$ echo bar >&${mycoproc[1]}
|
||||||
$ IFS= read -ru ${mycoproc[0]} x; printf '%s\n' "$x"
|
$ IFS= read -ru ${mycoproc[0]} x; printf '%s\n' "$x"
|
||||||
foobar
|
foobar
|
||||||
$ kill $mycoproc_PID
|
$ kill $mycoproc_PID
|
||||||
$
|
$
|
||||||
[1]+ Terminated coproc mycoproc { awk '{print "foo" $0;fflush()}'; }
|
[1]+ Terminated coproc mycoproc { awk '{print "foo" $0;fflush()}'; }
|
||||||
```
|
```
|
||||||
|
|
||||||
### Redirecting the output of a script to a file and to the screen
|
### Redirecting the output of a script to a file and to the screen
|
||||||
|
120
syntax/pe.md
120
syntax/pe.md
@ -91,8 +91,8 @@ be interpreted as part of the parameter name otherwise. Compare these
|
|||||||
two expressions (`WORD="car"` for example), where we want to print a
|
two expressions (`WORD="car"` for example), where we want to print a
|
||||||
word with a trailing "s":
|
word with a trailing "s":
|
||||||
|
|
||||||
echo "The plural of $WORD is most likely $WORDs"
|
echo "The plural of $WORD is most likely $WORDs"
|
||||||
echo "The plural of $WORD is most likely ${WORD}s"
|
echo "The plural of $WORD is most likely ${WORD}s"
|
||||||
|
|
||||||
<u>Why does the first one fail?</u> It prints nothing, because a
|
<u>Why does the first one fail?</u> It prints nothing, because a
|
||||||
parameter (variable) named "`WORDs`" is undefined and thus printed as ""
|
parameter (variable) named "`WORDs`" is undefined and thus printed as ""
|
||||||
@ -108,8 +108,8 @@ everything in UNIX(r)) **case sensitive!**
|
|||||||
The second form with the curly braces is also needed to access
|
The second form with the curly braces is also needed to access
|
||||||
positional parameters (arguments to a script) beyond `$9`:
|
positional parameters (arguments to a script) beyond `$9`:
|
||||||
|
|
||||||
echo "Argument 1 is: $1"
|
echo "Argument 1 is: $1"
|
||||||
echo "Argument 10 is: ${10}"
|
echo "Argument 10 is: ${10}"
|
||||||
|
|
||||||
### Simple usage: Arrays
|
### Simple usage: Arrays
|
||||||
|
|
||||||
@ -146,23 +146,23 @@ then `${!PARAMETER}` will expand to the value of the parameter named
|
|||||||
|
|
||||||
read -rep 'Which variable do you want to inspect? ' look_var
|
read -rep 'Which variable do you want to inspect? ' look_var
|
||||||
|
|
||||||
printf 'The value of "%s" is: "%s"\n' "$look_var" "${!look_var}"
|
printf 'The value of "%s" is: "%s"\n' "$look_var" "${!look_var}"
|
||||||
|
|
||||||
Of course the indirection also works with special variables:
|
Of course the indirection also works with special variables:
|
||||||
|
|
||||||
# set some fake positional parameters
|
# set some fake positional parameters
|
||||||
set one two three four
|
set one two three four
|
||||||
|
|
||||||
# get the LAST argument ("#" stores the number of arguments, so "!#" will reference the LAST argument)
|
# get the LAST argument ("#" stores the number of arguments, so "!#" will reference the LAST argument)
|
||||||
echo ${!#}
|
echo ${!#}
|
||||||
|
|
||||||
You can think of this mechanism as being roughly equivalent to taking
|
You can think of this mechanism as being roughly equivalent to taking
|
||||||
any parameter expansion that begins with the parameter name, and
|
any parameter expansion that begins with the parameter name, and
|
||||||
substituting the `!PARAMETER` part with the value of PARAMETER.
|
substituting the `!PARAMETER` part with the value of PARAMETER.
|
||||||
|
|
||||||
echo "${!var^^}"
|
echo "${!var^^}"
|
||||||
# ...is equivalent to
|
# ...is equivalent to
|
||||||
eval 'echo "${'"$var"'^^}"'
|
eval 'echo "${'"$var"'^^}"'
|
||||||
|
|
||||||
It was an unfortunate design decision to use the `!` prefix for
|
It was an unfortunate design decision to use the `!` prefix for
|
||||||
indirection, as it introduces parsing ambiguity with other parameter
|
indirection, as it introduces parsing ambiguity with other parameter
|
||||||
@ -220,7 +220,7 @@ of words in the variable while `~~` reverses case for all. Thanks to
|
|||||||
<u>**Example: Rename all `*.txt` filenames to lowercase**</u>
|
<u>**Example: Rename all `*.txt` filenames to lowercase**</u>
|
||||||
|
|
||||||
for file in *.txt; do
|
for file in *.txt; do
|
||||||
mv "$file" "${file,,}"
|
mv "$file" "${file,,}"
|
||||||
done
|
done
|
||||||
|
|
||||||
<u>**Note:**</u> Case modification is a handy feature you can apply to a
|
<u>**Note:**</u> Case modification is a handy feature you can apply to a
|
||||||
@ -255,7 +255,7 @@ Assume: `array=(This is some Text)`
|
|||||||
|
|
||||||
<!-- -->
|
<!-- -->
|
||||||
|
|
||||||
* ''echo "${array[2]^^}"''
|
* ''echo "${array[2]^^}"''
|
||||||
* => ''SOME''
|
* => ''SOME''
|
||||||
|
|
||||||
## Variable name expansion
|
## Variable name expansion
|
||||||
@ -293,7 +293,7 @@ interpreted just like a pattern to describe a filename to match
|
|||||||
|
|
||||||
Example string (*just a quote from a big man*):
|
Example string (*just a quote from a big man*):
|
||||||
|
|
||||||
MYSTRING="Be liberal in what you accept, and conservative in what you send"
|
MYSTRING="Be liberal in what you accept, and conservative in what you send"
|
||||||
|
|
||||||
### From the beginning
|
### From the beginning
|
||||||
|
|
||||||
@ -381,7 +381,7 @@ pattern](/syntax/pattern), on expansion time. The matched substring will
|
|||||||
be entirely removed and the given string will be inserted. Again some
|
be entirely removed and the given string will be inserted. Again some
|
||||||
example string for the tests:
|
example string for the tests:
|
||||||
|
|
||||||
MYSTRING="Be liberal in what you accept, and conservative in what you send"
|
MYSTRING="Be liberal in what you accept, and conservative in what you send"
|
||||||
|
|
||||||
The two main forms only differ in **the number of slashes** after the
|
The two main forms only differ in **the number of slashes** after the
|
||||||
parameter name: `${PARAMETER/PATTERN/STRING}` and
|
parameter name: `${PARAMETER/PATTERN/STRING}` and
|
||||||
@ -457,7 +457,7 @@ Assume: `array=(This is a text)`
|
|||||||
When you use this form, the length of the parameter's value is expanded.
|
When you use this form, the length of the parameter's value is expanded.
|
||||||
Again, a quote from a big man, to have a test text:
|
Again, a quote from a big man, to have a test text:
|
||||||
|
|
||||||
MYSTRING="Be liberal in what you accept, and conservative in what you send"
|
MYSTRING="Be liberal in what you accept, and conservative in what you send"
|
||||||
|
|
||||||
Using echo `${#MYSTRING}`...
|
Using echo `${#MYSTRING}`...
|
||||||
|
|
||||||
@ -551,7 +551,7 @@ If the `LENGTH` value is negative, it's used as offset from the end of
|
|||||||
the string. The expansion happens from the first to the second offset
|
the string. The expansion happens from the first to the second offset
|
||||||
then:
|
then:
|
||||||
|
|
||||||
echo "${MYSTRING:11:-17}"
|
echo "${MYSTRING:11:-17}"
|
||||||
|
|
||||||
=\>
|
=\>
|
||||||
`<del>Be liberal </del>in what you accept, and conservative<del> in what you send</del>`
|
`<del>Be liberal </del>in what you accept, and conservative<del> in what you send</del>`
|
||||||
@ -591,16 +591,16 @@ of `PARAMETER`, as if it just was `${PARAMETER}`. If you omit the `:`
|
|||||||
(colon), like shown in the second form, the default value is only used
|
(colon), like shown in the second form, the default value is only used
|
||||||
when the parameter was **unset**, not when it was empty.
|
when the parameter was **unset**, not when it was empty.
|
||||||
|
|
||||||
echo "Your home directory is: ${HOME:-/home/$USER}."
|
echo "Your home directory is: ${HOME:-/home/$USER}."
|
||||||
echo "${HOME:-/home/$USER} will be used to store your personal data."
|
echo "${HOME:-/home/$USER} will be used to store your personal data."
|
||||||
|
|
||||||
If `HOME` is unset or empty, everytime you want to print something
|
If `HOME` is unset or empty, everytime you want to print something
|
||||||
useful, you need to put that parameter syntax in.
|
useful, you need to put that parameter syntax in.
|
||||||
|
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
read -p "Enter your gender (just press ENTER to not tell us): " GENDER
|
read -p "Enter your gender (just press ENTER to not tell us): " GENDER
|
||||||
echo "Your gender is ${GENDER:-a secret}."
|
echo "Your gender is ${GENDER:-a secret}."
|
||||||
|
|
||||||
It will print "Your gender is a secret." when you don't enter the
|
It will print "Your gender is a secret." when you don't enter the
|
||||||
gender. Note that the default value is **used on expansion time**, it is
|
gender. Note that the default value is **used on expansion time**, it is
|
||||||
@ -650,14 +650,14 @@ Example code (please try the example cases yourself):
|
|||||||
|
|
||||||
|
|
||||||
### CASE 3: An array with only one element, a nullstring
|
### CASE 3: An array with only one element, a nullstring
|
||||||
array=("")
|
array=("")
|
||||||
|
|
||||||
echo ${array[@]:-This array is NULL or unset}
|
echo ${array[@]:-This array is NULL or unset}
|
||||||
echo ${array[@]-This array is NULL or unset}
|
echo ${array[@]-This array is NULL or unset}
|
||||||
|
|
||||||
|
|
||||||
### CASE 4: An array with only two elements, a nullstring and a normal word
|
### CASE 4: An array with only two elements, a nullstring and a normal word
|
||||||
array=("" word)
|
array=("" word)
|
||||||
|
|
||||||
echo ${array[@]:-This array is NULL or unset}
|
echo ${array[@]:-This array is NULL or unset}
|
||||||
echo ${array[@]-This array is NULL or unset}
|
echo ${array[@]-This array is NULL or unset}
|
||||||
@ -675,8 +675,8 @@ unset or null. Equivalent to using a default value, when you omit the
|
|||||||
`:` (colon), as shown in the second form, the default value will only be
|
`:` (colon), as shown in the second form, the default value will only be
|
||||||
assigned when the parameter was **unset**.
|
assigned when the parameter was **unset**.
|
||||||
|
|
||||||
echo "Your home directory is: ${HOME:=/home/$USER}."
|
echo "Your home directory is: ${HOME:=/home/$USER}."
|
||||||
echo "$HOME will be used to store your personal data."
|
echo "$HOME will be used to store your personal data."
|
||||||
|
|
||||||
After the first expansion here (`${HOME:=/home/$USER}`), `HOME` is set
|
After the first expansion here (`${HOME:=/home/$USER}`), `HOME` is set
|
||||||
and usable.
|
and usable.
|
||||||
@ -685,9 +685,9 @@ Let's change our code example from above:
|
|||||||
|
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
read -p "Enter your gender (just press ENTER to not tell us): " GENDER
|
read -p "Enter your gender (just press ENTER to not tell us): " GENDER
|
||||||
echo "Your gender is ${GENDER:=a secret}."
|
echo "Your gender is ${GENDER:=a secret}."
|
||||||
echo "Ah, in case you forgot, your gender is really: $GENDER"
|
echo "Ah, in case you forgot, your gender is really: $GENDER"
|
||||||
|
|
||||||
### Assign a default value: Arrays
|
### Assign a default value: Arrays
|
||||||
|
|
||||||
@ -707,7 +707,7 @@ This form expands to nothing if the parameter is unset or empty. If it
|
|||||||
is set, it does not expand to the parameter's value, **but to some text
|
is set, it does not expand to the parameter's value, **but to some text
|
||||||
you can specify**:
|
you can specify**:
|
||||||
|
|
||||||
echo "The Java application was installed and can be started.${JAVAPATH:+ NOTE: JAVAPATH seems to be set}"
|
echo "The Java application was installed and can be started.${JAVAPATH:+ NOTE: JAVAPATH seems to be set}"
|
||||||
|
|
||||||
The above code will simply add a warning if `JAVAPATH` is set (because
|
The above code will simply add a warning if `JAVAPATH` is set (because
|
||||||
it could influence the startup behaviour of that imaginary application).
|
it could influence the startup behaviour of that imaginary application).
|
||||||
@ -718,8 +718,8 @@ flags:
|
|||||||
|
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
read -p "If you want to use special flags, enter them now: " SPECIAL_FLAGS
|
read -p "If you want to use special flags, enter them now: " SPECIAL_FLAGS
|
||||||
echo "The installation of the application is finished${SPECIAL_FLAGS:+ (NOTE: there are special flags set: $SPECIAL_FLAGS)}."
|
echo "The installation of the application is finished${SPECIAL_FLAGS:+ (NOTE: there are special flags set: $SPECIAL_FLAGS)}."
|
||||||
|
|
||||||
If you omit the colon, as shown in the second form
|
If you omit the colon, as shown in the second form
|
||||||
(`${PARAMETER+WORD}`), the alternate value will be used if the parameter
|
(`${PARAMETER+WORD}`), the alternate value will be used if the parameter
|
||||||
@ -729,13 +729,13 @@ if variables you need (and that can be empty) are undefined:
|
|||||||
# test that with the three stages:
|
# test that with the three stages:
|
||||||
|
|
||||||
# unset foo
|
# unset foo
|
||||||
# foo=""
|
# foo=""
|
||||||
# foo="something"
|
# foo="something"
|
||||||
|
|
||||||
if [[ ${foo+isset} = isset ]]; then
|
if [[ ${foo+isset} = isset ]]; then
|
||||||
echo "foo is set..."
|
echo "foo is set..."
|
||||||
else
|
else
|
||||||
echo "foo is not set..."
|
echo "foo is not set..."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
### Use an alternate value: Arrays
|
### Use an alternate value: Arrays
|
||||||
@ -763,7 +763,7 @@ If the parameter `PARAMETER` is set/non-null, this form will simply
|
|||||||
expand it. Otherwise, the expansion of `WORD` will be used as appendix
|
expand it. Otherwise, the expansion of `WORD` will be used as appendix
|
||||||
for an error message:
|
for an error message:
|
||||||
|
|
||||||
$ echo "The unset parameter is: ${p_unset?not set}"
|
$ echo "The unset parameter is: ${p_unset?not set}"
|
||||||
bash: p_unset: not set
|
bash: p_unset: not set
|
||||||
|
|
||||||
After printing this message,
|
After printing this message,
|
||||||
@ -785,13 +785,13 @@ are taken into account.
|
|||||||
|
|
||||||
Removing the first 6 characters from a text string:
|
Removing the first 6 characters from a text string:
|
||||||
|
|
||||||
STRING="Hello world"
|
STRING="Hello world"
|
||||||
|
|
||||||
# only print 'Hello'
|
# only print 'Hello'
|
||||||
echo "${STRING%??????}"
|
echo "${STRING%??????}"
|
||||||
|
|
||||||
# only print 'world'
|
# only print 'world'
|
||||||
echo "${STRING#??????}"
|
echo "${STRING#??????}"
|
||||||
|
|
||||||
# store it into the same variable
|
# store it into the same variable
|
||||||
STRING=${STRING#??????}
|
STRING=${STRING#??????}
|
||||||
@ -806,7 +806,7 @@ Removing the first 6 characters from a text string:
|
|||||||
parameters plus the adjacent expansion are concatenated into a single
|
parameters plus the adjacent expansion are concatenated into a single
|
||||||
argument. As a workaround, each expansion needs to be quoted
|
argument. As a workaround, each expansion needs to be quoted
|
||||||
separately. Unfortunately, this bug took a very long time to
|
separately. Unfortunately, this bug took a very long time to
|
||||||
notice.`~ $ set -- a b c; x=foo; printf '<%s> ' "$@$x" "$*""$x" "$@""$x"
|
notice.`~ $ set -- a b c; x=foo; printf '<%s> ' "$@$x" "$*""$x" "$@""$x"
|
||||||
<a b cfoo> <a b cfoo> <a> <b> <cfoo>
|
<a b cfoo> <a b cfoo> <a> <b> <cfoo>
|
||||||
`
|
`
|
||||||
|
|
||||||
@ -823,14 +823,14 @@ Removing the first 6 characters from a text string:
|
|||||||
unquoted, this should be a rare issue. **Always quote
|
unquoted, this should be a rare issue. **Always quote
|
||||||
them**!`touch x 'y z'
|
them**!`touch x 'y z'
|
||||||
for sh in bb {{d,b}a,{m,}k,z}sh; do
|
for sh in bb {{d,b}a,{m,}k,z}sh; do
|
||||||
echo "$sh"
|
echo "$sh"
|
||||||
"$sh" -s a 'b c' d \* </dev/fd/0
|
"$sh" -s a 'b c' d \* </dev/fd/0
|
||||||
done <<\EOF
|
done <<\EOF
|
||||||
${ZSH_VERSION+:} false && emulate sh
|
${ZSH_VERSION+:} false && emulate sh
|
||||||
IFS=
|
IFS=
|
||||||
printf '<%s> ' $*
|
printf '<%s> ' $*
|
||||||
echo
|
echo
|
||||||
printf "<%s> " $@
|
printf "<%s> " $@
|
||||||
echo
|
echo
|
||||||
EOF
|
EOF
|
||||||
``bb
|
``bb
|
||||||
@ -863,13 +863,13 @@ Removing the first 6 characters from a text string:
|
|||||||
the way adjacent arguments are concatenated, when IFS is modified in
|
the way adjacent arguments are concatenated, when IFS is modified in
|
||||||
the middle of expansion through
|
the middle of expansion through
|
||||||
side-effects.`for sh in bb {{d,b}a,po,{m,}k,z}sh; do
|
side-effects.`for sh in bb {{d,b}a,po,{m,}k,z}sh; do
|
||||||
printf '%-4s: ' "$sh"
|
printf '%-4s: ' "$sh"
|
||||||
"$sh" </dev/fd/0
|
"$sh" </dev/fd/0
|
||||||
done <<\EOF
|
done <<\EOF
|
||||||
${ZSH_VERSION+:} false && emulate sh
|
${ZSH_VERSION+:} false && emulate sh
|
||||||
set -f -- a b c
|
set -f -- a b c
|
||||||
unset -v IFS
|
unset -v IFS
|
||||||
printf '<%s> ' ${*}${IFS=}${*}${IFS:=-}"${*}"
|
printf '<%s> ' ${*}${IFS=}${*}${IFS:=-}"${*}"
|
||||||
echo
|
echo
|
||||||
EOF
|
EOF
|
||||||
``bb : <a b cabc> <a-b-c>
|
``bb : <a b cabc> <a-b-c>
|
||||||
@ -913,14 +913,14 @@ Removing the first 6 characters from a text string:
|
|||||||
For ranges, Bash evaluates as little as possible, i.e., if the first
|
For ranges, Bash evaluates as little as possible, i.e., if the first
|
||||||
part is out of range, the second won't be evaluated. ksh93 and mksh
|
part is out of range, the second won't be evaluated. ksh93 and mksh
|
||||||
always evaluate the subscript parts even if the parameter is unset.
|
always evaluate the subscript parts even if the parameter is unset.
|
||||||
` $ bash -c 'n="y[\$(printf yo >&2)1]" m="y[\$(printf jo >&2)1]"; x=(); echo "${x[@]:n,6:m}"' # No output
|
` $ bash -c 'n="y[\$(printf yo >&2)1]" m="y[\$(printf jo >&2)1]"; x=(); echo "${x[@]:n,6:m}"' # No output
|
||||||
$ bash -c 'n="y[\$(printf yo >&2)1]" m="y[\$(printf jo >&2)1]"; x=([5]=hi); echo "${x[@]:n,6:m}"'
|
$ bash -c 'n="y[\$(printf yo >&2)1]" m="y[\$(printf jo >&2)1]"; x=([5]=hi); echo "${x[@]:n,6:m}"'
|
||||||
yo
|
yo
|
||||||
$ bash -c 'n="y[\$(printf yo >&2)1]" m="y[\$(printf jo >&2)1]"; x=([6]=hi); echo "${x[@]:n,6:m}"'
|
$ bash -c 'n="y[\$(printf yo >&2)1]" m="y[\$(printf jo >&2)1]"; x=([6]=hi); echo "${x[@]:n,6:m}"'
|
||||||
yojo
|
yojo
|
||||||
$ bash -c 'n="y[\$(printf yo >&2)1]" m="y[\$(printf jo >&2)1]"; x=12345; echo "${x:n,5:m}"'
|
$ bash -c 'n="y[\$(printf yo >&2)1]" m="y[\$(printf jo >&2)1]"; x=12345; echo "${x:n,5:m}"'
|
||||||
yojo
|
yojo
|
||||||
$ bash -c 'n="y[\$(printf yo >&2)1]" m="y[\$(printf jo >&2)1]"; x=12345; echo "${x:n,6:m}"'
|
$ bash -c 'n="y[\$(printf yo >&2)1]" m="y[\$(printf jo >&2)1]"; x=12345; echo "${x:n,6:m}"'
|
||||||
yo
|
yo
|
||||||
`
|
`
|
||||||
|
|
||||||
@ -935,12 +935,12 @@ Removing the first 6 characters from a text string:
|
|||||||
# Bash
|
# Bash
|
||||||
$ typeset -a a=(meh bleh blerg) b
|
$ typeset -a a=(meh bleh blerg) b
|
||||||
$ IFS=e
|
$ IFS=e
|
||||||
$ printf "<%s> " "${b[@]-"${a[@]}" "${a[@]}"}"; echo # The entire PE is quoted so Bash considers the inner quotes redundant.
|
$ printf "<%s> " "${b[@]-"${a[@]}" "${a[@]}"}"; echo # The entire PE is quoted so Bash considers the inner quotes redundant.
|
||||||
<meh> <bleh> <blerg meh> <bleh> <blerg>
|
<meh> <bleh> <blerg meh> <bleh> <blerg>
|
||||||
$ printf "<%s> " "${b[@]-${a[@]} ${a[@]}}"; echo # The outer quotes cause the inner expansions to be considered quoted.
|
$ printf "<%s> " "${b[@]-${a[@]} ${a[@]}}"; echo # The outer quotes cause the inner expansions to be considered quoted.
|
||||||
<meh> <bleh> <blerg meh> <bleh> <blerg>
|
<meh> <bleh> <blerg meh> <bleh> <blerg>
|
||||||
$ b=(meep beep)
|
$ b=(meep beep)
|
||||||
$ printf "<%s> " "${b[@]-"${a[@]}" "${a[@]}"}" "${b[@]-${a[@]} ${a[@]}}"; echo # Again no surprises. Outer quotes quote everything recursively.
|
$ printf "<%s> " "${b[@]-"${a[@]}" "${a[@]}"}" "${b[@]-${a[@]} ${a[@]}}"; echo # Again no surprises. Outer quotes quote everything recursively.
|
||||||
<meep> <beep> <meep> <beep>
|
<meep> <beep> <meep> <beep>
|
||||||
|
|
||||||
Now lets see what can happen if we leave the outside unquoted.
|
Now lets see what can happen if we leave the outside unquoted.
|
||||||
@ -948,9 +948,9 @@ Now lets see what can happen if we leave the outside unquoted.
|
|||||||
# Bash
|
# Bash
|
||||||
$ typeset -a a=(meh bleh blerg) b
|
$ typeset -a a=(meh bleh blerg) b
|
||||||
$ IFS=e
|
$ IFS=e
|
||||||
$ printf "<%s> " ${b[@]-"${a[@]}" "${a[@]}"}; echo # Inner quotes make inner expansions quoted.
|
$ printf "<%s> " ${b[@]-"${a[@]}" "${a[@]}"}; echo # Inner quotes make inner expansions quoted.
|
||||||
<meh> <bleh> <blerg meh> <bleh> <blerg>
|
<meh> <bleh> <blerg meh> <bleh> <blerg>
|
||||||
$ printf "<%s> " ${b[@]-${a[@]} ${a[@]}}; echo' # No quotes at all wordsplits / globs, like you'd expect.
|
$ printf "<%s> " ${b[@]-${a[@]} ${a[@]}}; echo' # No quotes at all wordsplits / globs, like you'd expect.
|
||||||
<m> <h> <bl> <h> <bl> <rg m> <h> <bl> <h> <bl> <rg>
|
<m> <h> <bl> <h> <bl> <rg m> <h> <bl> <h> <bl> <rg>
|
||||||
|
|
||||||
This all might be intuitive, and is the most common implementation, but
|
This all might be intuitive, and is the most common implementation, but
|
||||||
@ -976,7 +976,7 @@ Consider the following (only slightly far-fetched) code:
|
|||||||
# What do you think the programmer expected to happen here?
|
# What do you think the programmer expected to happen here?
|
||||||
# What do you think will actually happen...
|
# What do you think will actually happen...
|
||||||
|
|
||||||
"${someCmd[@]-"$someOtherCmd" arg2 "${otherArgs[@]}"}" arg5
|
"${someCmd[@]-"$someOtherCmd" arg2 "${otherArgs[@]}"}" arg5
|
||||||
|
|
||||||
This final line is perhaps not the most obvious, but I've run into cases
|
This final line is perhaps not the most obvious, but I've run into cases
|
||||||
were this type of logic can be desirable and realistic. We can deduce
|
were this type of logic can be desirable and realistic. We can deduce
|
||||||
@ -996,20 +996,20 @@ outer expansion correctly without word-splitting or globbing is to quote
|
|||||||
it. Bash will actually expand the command as one of these:
|
it. Bash will actually expand the command as one of these:
|
||||||
|
|
||||||
# The quoted PE produces a correct result here...
|
# The quoted PE produces a correct result here...
|
||||||
$ bash -c 'typeset -a someCmd=(myCmd arg1 "arg2 yay!" "third*arg*" 4); printf "<%s> " "${someCmd[@]-"$someOtherCmd" arg2 "${otherArgs[@]}"}" arg5; echo'
|
$ bash -c 'typeset -a someCmd=(myCmd arg1 "arg2 yay!" "third*arg*" 4); printf "<%s> " "${someCmd[@]-"$someOtherCmd" arg2 "${otherArgs[@]}"}" arg5; echo'
|
||||||
<myCmd> <arg1> <arg2 yay!> <third*arg*> <4> <arg5>
|
<myCmd> <arg1> <arg2 yay!> <third*arg*> <4> <arg5>
|
||||||
|
|
||||||
# ...but in the opposite case the first 3 arguments are glued together. There are no workarounds.
|
# ...but in the opposite case the first 3 arguments are glued together. There are no workarounds.
|
||||||
$ bash -c 'typeset -a otherArgs=(arg3 arg4); someOtherCmd=mycommand; printf "<%s> " "${someCmd[@]-"$someOtherCmd" arg2 "${otherArgs[@]}"}" arg5; echo'
|
$ bash -c 'typeset -a otherArgs=(arg3 arg4); someOtherCmd=mycommand; printf "<%s> " "${someCmd[@]-"$someOtherCmd" arg2 "${otherArgs[@]}"}" arg5; echo'
|
||||||
<mycommand arg2 arg3> <arg4> <arg5>
|
<mycommand arg2 arg3> <arg4> <arg5>
|
||||||
|
|
||||||
# UNLESS! we unquote the outer expansion allowing the inner quotes to
|
# UNLESS! we unquote the outer expansion allowing the inner quotes to
|
||||||
# affect the necessary parts while allowing word-splitting to split the literals:
|
# affect the necessary parts while allowing word-splitting to split the literals:
|
||||||
$ bash -c 'typeset -a otherArgs=(arg3 arg4); someOtherCmd=mycommand; printf "<%s> " ${someCmd[@]-"$someOtherCmd" arg2 "${otherArgs[@]}"} arg5; echo'
|
$ bash -c 'typeset -a otherArgs=(arg3 arg4); someOtherCmd=mycommand; printf "<%s> " ${someCmd[@]-"$someOtherCmd" arg2 "${otherArgs[@]}"} arg5; echo'
|
||||||
<mycommand> <arg2> <arg3> <arg4> <arg5>
|
<mycommand> <arg2> <arg3> <arg4> <arg5>
|
||||||
|
|
||||||
# Success!!!
|
# Success!!!
|
||||||
$ bash -c 'typeset -a someCmd=(myCmd arg1 "arg2 yay!" "third*arg*" 4); printf "<%s> " ${someCmd[@]-"$someOtherCmd" arg2 "${otherArgs[@]}"} arg5; echo'
|
$ bash -c 'typeset -a someCmd=(myCmd arg1 "arg2 yay!" "third*arg*" 4); printf "<%s> " ${someCmd[@]-"$someOtherCmd" arg2 "${otherArgs[@]}"} arg5; echo'
|
||||||
<myCmd> <arg1> <arg2> <yay!> <third*arg*> <4> <arg5>
|
<myCmd> <arg1> <arg2> <yay!> <third*arg*> <4> <arg5>
|
||||||
|
|
||||||
# ...Ah f^^k. (again, no workaround possible.)
|
# ...Ah f^^k. (again, no workaround possible.)
|
||||||
@ -1027,10 +1027,10 @@ All we need to do is add two extra double-quotes:
|
|||||||
|
|
||||||
# ksh93 passing the two failed tests from above:
|
# ksh93 passing the two failed tests from above:
|
||||||
|
|
||||||
$ ksh -c 'otherArgs=(arg3 arg4); someOtherCmd="mycommand"; printf "<%s> " "${someCmd[@]-""$someOtherCmd" arg2 "${otherArgs[@]}""}" arg5; echo'
|
$ ksh -c 'otherArgs=(arg3 arg4); someOtherCmd="mycommand"; printf "<%s> " "${someCmd[@]-""$someOtherCmd" arg2 "${otherArgs[@]}""}" arg5; echo'
|
||||||
<mycommand> <arg2> <arg3> <arg4> <arg5>
|
<mycommand> <arg2> <arg3> <arg4> <arg5>
|
||||||
|
|
||||||
$ ksh -c 'typeset -a someCmd=(myCmd arg1 "arg2 yay!" "third*arg*" 4); printf "<%s> " "${someCmd[@]-""$someOtherCmd" arg2 "${otherArgs[@]}""}" arg5; echo'
|
$ ksh -c 'typeset -a someCmd=(myCmd arg1 "arg2 yay!" "third*arg*" 4); printf "<%s> " "${someCmd[@]-""$someOtherCmd" arg2 "${otherArgs[@]}""}" arg5; echo'
|
||||||
<myCmd> <arg1> <arg2 yay!> <third*arg*> <4> <arg5>
|
<myCmd> <arg1> <arg2 yay!> <third*arg*> <4> <arg5>
|
||||||
|
|
||||||
This can be used to control the quote state of any part of any expansion
|
This can be used to control the quote state of any part of any expansion
|
||||||
|
@ -21,18 +21,18 @@ related to the quote characters passed as text to the command line! The
|
|||||||
syntax quotes are removed before the command is called! Example:
|
syntax quotes are removed before the command is called! Example:
|
||||||
|
|
||||||
### NO NO NO: this passes three strings:
|
### NO NO NO: this passes three strings:
|
||||||
### (1) "my
|
### (1) "my
|
||||||
### (2) multiword
|
### (2) multiword
|
||||||
### (3) argument"
|
### (3) argument"
|
||||||
MYARG="\"my multiword argument\""
|
MYARG="\"my multiword argument\""
|
||||||
somecommand $MYARG
|
somecommand $MYARG
|
||||||
|
|
||||||
### THIS IS NOT (!) THE SAME AS ###
|
### THIS IS NOT (!) THE SAME AS ###
|
||||||
command "my multiword argument"
|
command "my multiword argument"
|
||||||
|
|
||||||
### YOU NEED ###
|
### YOU NEED ###
|
||||||
MYARG="my multiword argument"
|
MYARG="my multiword argument"
|
||||||
command "$MYARG"
|
command "$MYARG"
|
||||||
|
|
||||||
## Per-character escaping
|
## Per-character escaping
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ general, a character that has a special meaning to Bash, like the
|
|||||||
dollar-sign (`$`) can be masked to not have a special meaning using the
|
dollar-sign (`$`) can be masked to not have a special meaning using the
|
||||||
backslash:
|
backslash:
|
||||||
|
|
||||||
echo \$HOME is set to \"$HOME\"
|
echo \$HOME is set to \"$HOME\"
|
||||||
|
|
||||||
- `\$HOME` won't expand because it's not in variable-expansion syntax
|
- `\$HOME` won't expand because it's not in variable-expansion syntax
|
||||||
anymore
|
anymore
|
||||||
@ -81,12 +81,12 @@ Inside a weak-quoted string there's **no special interpretion of**:
|
|||||||
Everything else, especially [parameter expansion](/syntax/pe), is
|
Everything else, especially [parameter expansion](/syntax/pe), is
|
||||||
performed!
|
performed!
|
||||||
|
|
||||||
ls -l "*"
|
ls -l "*"
|
||||||
|
|
||||||
Will not be expanded. `ls` gets the literal `*` as argument. It will,
|
Will not be expanded. `ls` gets the literal `*` as argument. It will,
|
||||||
unless you have a file named `*`, spit out an error.
|
unless you have a file named `*`, spit out an error.
|
||||||
|
|
||||||
echo "Your PATH is: $PATH"
|
echo "Your PATH is: $PATH"
|
||||||
|
|
||||||
Will work as expected. `$PATH` is expanded, because it's double (weak)
|
Will work as expected. `$PATH` is expanded, because it's double (weak)
|
||||||
quoted.
|
quoted.
|
||||||
@ -126,7 +126,7 @@ to get the character "`'`" as literal text:
|
|||||||
echo 'Here'\''s my test...'
|
echo 'Here'\''s my test...'
|
||||||
|
|
||||||
# ALTERNATIVE: It's also possible to mix-and-match quotes for readability:
|
# ALTERNATIVE: It's also possible to mix-and-match quotes for readability:
|
||||||
echo "Here's my test"
|
echo "Here's my test"
|
||||||
|
|
||||||
## ANSI C like strings
|
## ANSI C like strings
|
||||||
|
|
||||||
@ -174,7 +174,7 @@ compatibility" features).
|
|||||||
|
|
||||||
A dollar-sign followed by a double-quoted string, for example
|
A dollar-sign followed by a double-quoted string, for example
|
||||||
|
|
||||||
echo $"generating database..."
|
echo $"generating database..."
|
||||||
|
|
||||||
means I18N. If there is a translation available for that string, it is
|
means I18N. If there is a translation available for that string, it is
|
||||||
used instead of the given text. If not, or if the locale is `C`/`POSIX`,
|
used instead of the given text. If not, or if the locale is `C`/`POSIX`,
|
||||||
@ -200,11 +200,11 @@ documentation](http://www.gnu.org/software/gettext/manual/html_node/bash.html)
|
|||||||
The [classic for loop](/syntax/ccmd/classic_for) uses a list of words to
|
The [classic for loop](/syntax/ccmd/classic_for) uses a list of words to
|
||||||
iterate through. The list can also be in a variable:
|
iterate through. The list can also be in a variable:
|
||||||
|
|
||||||
mylist="DOG CAT BIRD HORSE"
|
mylist="DOG CAT BIRD HORSE"
|
||||||
|
|
||||||
**<u>WRONG</u>** way to iterate through this list:
|
**<u>WRONG</u>** way to iterate through this list:
|
||||||
|
|
||||||
for animal in "$mylist"; do
|
for animal in "$mylist"; do
|
||||||
echo $animal
|
echo $animal
|
||||||
done
|
done
|
||||||
|
|
||||||
@ -235,7 +235,7 @@ writing this as a test command it would be:
|
|||||||
When you compare variables, it's wise to quote them. Let's create a test
|
When you compare variables, it's wise to quote them. Let's create a test
|
||||||
string with spaces:
|
string with spaces:
|
||||||
|
|
||||||
mystring="my string"
|
mystring="my string"
|
||||||
|
|
||||||
And now check that string against the word "testword":
|
And now check that string against the word "testword":
|
||||||
|
|
||||||
@ -251,7 +251,7 @@ Which is wrong, because `my` and `string` are two separate arguments.
|
|||||||
|
|
||||||
So what you really want to do is:
|
So what you really want to do is:
|
||||||
|
|
||||||
[ "$mystring" = testword ] # RIGHT!
|
[ "$mystring" = testword ] # RIGHT!
|
||||||
|
|
||||||
test 'my string' = testword
|
test 'my string' = testword
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ Whenever you **reference** a descriptor, to point to its current target
|
|||||||
file, then you use a "`&`" followed by a the descriptor number:
|
file, then you use a "`&`" followed by a the descriptor number:
|
||||||
|
|
||||||
# this executes the echo-command and redirects its normal output (stdout) to the standard error target
|
# this executes the echo-command and redirects its normal output (stdout) to the standard error target
|
||||||
echo "There was an error" 1>&2
|
echo "There was an error" 1>&2
|
||||||
|
|
||||||
The redirection operation can be **anywhere** in a simple command, so
|
The redirection operation can be **anywhere** in a simple command, so
|
||||||
these examples are equivalent:
|
these examples are equivalent:
|
||||||
@ -166,7 +166,7 @@ substitutions and expansions are performed in the here-document data:
|
|||||||
|
|
||||||
You can avoid that by quoting the tag:
|
You can avoid that by quoting the tag:
|
||||||
|
|
||||||
cat <<"EOF"
|
cat <<"EOF"
|
||||||
This won't be expanded: $PATH
|
This won't be expanded: $PATH
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
@ -191,7 +191,7 @@ end-of-file is seen before the tag is reached.\</wrap\>
|
|||||||
The here-strings are a variation of the here-documents. The word `WORD`
|
The here-strings are a variation of the here-documents. The word `WORD`
|
||||||
is taken for the input redirection:
|
is taken for the input redirection:
|
||||||
|
|
||||||
cat <<< "Hello world... $NAME is here..."
|
cat <<< "Hello world... $NAME is here..."
|
||||||
|
|
||||||
Just beware to quote the `WORD` if it contains spaces. Otherwise the
|
Just beware to quote the `WORD` if it contains spaces. Otherwise the
|
||||||
rest will be given as normal parameters.
|
rest will be given as normal parameters.
|
||||||
|
@ -1238,7 +1238,7 @@ current mailfile.
|
|||||||
|
|
||||||
Example content:
|
Example content:
|
||||||
|
|
||||||
/var/mail/bfox?"You have mail":~/shell-mail?"$_ has mail!"
|
/var/mail/bfox?"You have mail":~/shell-mail?"$_ has mail!"
|
||||||
|
|
||||||
### OPTERR
|
### OPTERR
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ context of command-splitting, which this section is about, it doesn't
|
|||||||
matter which kind of quoting you use: weak quoting or strong quoting,
|
matter which kind of quoting you use: weak quoting or strong quoting,
|
||||||
both cause Bash to not treat spaces as special characters:
|
both cause Bash to not treat spaces as special characters:
|
||||||
|
|
||||||
$ echo "Hello little world"
|
$ echo "Hello little world"
|
||||||
Hello little world
|
Hello little world
|
||||||
|
|
||||||
$ echo 'Hello little world'
|
$ echo 'Hello little world'
|
||||||
@ -80,7 +80,7 @@ spaces in them:
|
|||||||
$ cat test\ file
|
$ cat test\ file
|
||||||
m00!
|
m00!
|
||||||
|
|
||||||
$ cat "test file"
|
$ cat "test file"
|
||||||
m00!
|
m00!
|
||||||
|
|
||||||
If you enter that on the command line with Tab completion, that will
|
If you enter that on the command line with Tab completion, that will
|
||||||
@ -103,7 +103,7 @@ second type of word splitting comes in - several expansions undergo
|
|||||||
|
|
||||||
Imagine you have a filename stored in a variable:
|
Imagine you have a filename stored in a variable:
|
||||||
|
|
||||||
MYFILE="test file"
|
MYFILE="test file"
|
||||||
|
|
||||||
When this variable is used, its occurance will be replaced by its
|
When this variable is used, its occurance will be replaced by its
|
||||||
content.
|
content.
|
||||||
@ -116,7 +116,7 @@ Though this is another step where spaces make things difficult,
|
|||||||
**quoting** is used to work around the difficulty. Quotes also affect
|
**quoting** is used to work around the difficulty. Quotes also affect
|
||||||
word splitting:
|
word splitting:
|
||||||
|
|
||||||
$ cat "$MYFILE"
|
$ cat "$MYFILE"
|
||||||
m00!
|
m00!
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
@ -124,7 +124,7 @@ word splitting:
|
|||||||
Let's follow an unquoted command through these steps, assuming that the
|
Let's follow an unquoted command through these steps, assuming that the
|
||||||
variable is set:
|
variable is set:
|
||||||
|
|
||||||
MYFILE="THE FILE.TXT"
|
MYFILE="THE FILE.TXT"
|
||||||
|
|
||||||
and the first review is:
|
and the first review is:
|
||||||
|
|
||||||
@ -149,7 +149,7 @@ splitting](/syntax/expansion/wordsplit) on the results:
|
|||||||
|
|
||||||
Now let's imagine we quoted `$MYFILE`, the command line now looks like:
|
Now let's imagine we quoted `$MYFILE`, the command line now looks like:
|
||||||
|
|
||||||
echo The file is named "$MYFILE"
|
echo The file is named "$MYFILE"
|
||||||
|
|
||||||
| Word splitting after substitution (quoted!): | | | | | |
|
| Word splitting after substitution (quoted!): | | | | | |
|
||||||
|----------------------------------------------|--------|--------|--------|---------|----------------|
|
|----------------------------------------------|--------|--------|--------|---------|----------------|
|
||||||
|
Loading…
Reference in New Issue
Block a user