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
|
||||
# We also take the opportunity to convert some HTML chars
|
||||
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 ######
|
||||
|
6
bash4.md
6
bash4.md
@ -152,9 +152,9 @@ something like
|
||||
|
||||
declare -A ASSOC
|
||||
|
||||
ASSOC[First]="first element"
|
||||
ASSOC[Hello]="second element"
|
||||
ASSOC[Peter Pan]="A weird guy"
|
||||
ASSOC[First]="first element"
|
||||
ASSOC[Hello]="second element"
|
||||
ASSOC[Peter Pan]="A weird guy"
|
||||
|
||||
See [arrays](/syntax/arrays)
|
||||
|
||||
|
@ -38,11 +38,11 @@ die() {
|
||||
while caller $frame; do
|
||||
((++frame));
|
||||
done
|
||||
echo "$*"
|
||||
echo "$*"
|
||||
exit 1
|
||||
}
|
||||
|
||||
f1() { die "*** an error occured ***"; }
|
||||
f1() { die "*** an error occured ***"; }
|
||||
f2() { f1; }
|
||||
f3() { f2; }
|
||||
|
||||
|
@ -79,11 +79,11 @@ assignment.
|
||||
$ declare -f
|
||||
foo ()
|
||||
{
|
||||
echo "FOO is BAR"
|
||||
echo "FOO is BAR"
|
||||
}
|
||||
world ()
|
||||
{
|
||||
echo "Hello World!"
|
||||
echo "Hello World!"
|
||||
}
|
||||
|
||||
...or just a specific defined function.
|
||||
@ -91,7 +91,7 @@ assignment.
|
||||
$ declare -f foo
|
||||
foo ()
|
||||
{
|
||||
echo "FOO is BAR"
|
||||
echo "FOO is BAR"
|
||||
}
|
||||
|
||||
### Nameref
|
||||
@ -107,15 +107,15 @@ variable whose name is expanded on the RHS.
|
||||
typeset -n _result=$1 _arr
|
||||
typeset IFS=+
|
||||
_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[*]} ))
|
||||
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
|
||||
}
|
||||
|
||||
a=(1 2 3) b=(6 5 4) c=(2 4 6)
|
||||
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 {
|
||||
|
||||
@ -123,10 +123,10 @@ variable whose name is expanded on the RHS.
|
||||
shift
|
||||
typeset IFS=+ _arrx
|
||||
_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
|
||||
(( _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
|
||||
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ the `eval` command below it.
|
||||
... arbitrary bash code here ...
|
||||
EOF
|
||||
|
||||
eval "$myCode"
|
||||
eval "$myCode"
|
||||
|
||||
### 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.
|
||||
|
||||
main() {
|
||||
local fun='() { echo "$FUNCNAME"; }' x
|
||||
local fun='() { echo "$FUNCNAME"; }' x
|
||||
|
||||
for x in {f..n}; do
|
||||
eval "${x}${fun}"
|
||||
eval "${x}${fun}"
|
||||
done
|
||||
|
||||
"$@"
|
||||
"$@"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
main "$@"
|
||||
|
||||
### 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
|
||||
through printf requiring another `eval` to peel off the escaping again.
|
||||
|
||||
while (( ++n <= 5 )) || ! evalBall="eval $evalBall"; do
|
||||
printf -v evalBall 'eval %q' "printf $n;${evalBall-printf '0\n'}"
|
||||
while (( ++n <= 5 )) || ! evalBall="eval $evalBall"; do
|
||||
printf -v evalBall 'eval %q' "printf $n;${evalBall-printf '0\n'}"
|
||||
done
|
||||
$evalBall
|
||||
|
||||
@ -78,19 +78,19 @@ application](http://en.wikipedia.org/wiki/Partial_application) using
|
||||
`eval`.
|
||||
|
||||
function partial {
|
||||
eval shift 2 \; function "$1" \{ "$2" "$(printf '%q ' "${@:3}")" '"$@"; }'
|
||||
eval shift 2 \; function "$1" \{ "$2" "$(printf '%q ' "${@:3}")" '"$@"; }'
|
||||
}
|
||||
|
||||
function repeat {
|
||||
[[ $1 == +([0-9]) ]] || return
|
||||
typeset n
|
||||
while ((n++ < $1)); do
|
||||
"${@:2}"
|
||||
"${@:2}"
|
||||
done
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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>
|
||||
$ ( 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 `('
|
||||
$ ( 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>
|
||||
|
||||
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
|
||||
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)'
|
||||
+ eval 'x+=(\[[123]\]=*)' 'x+=([3]=yo)'
|
||||
++ x+=(\[[123]\]=*)
|
||||
|
@ -38,12 +38,12 @@ shell without executing any program.
|
||||
|
||||
``` bash
|
||||
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.
|
||||
# well, stuff a wrapper is there for
|
||||
|
||||
exec "$myprog" "$@"
|
||||
exec "$myprog" "$@"
|
||||
```
|
||||
|
||||
### 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
|
||||
SSH sessions):
|
||||
|
||||
export DISPLAY=":0"
|
||||
export DISPLAY=":0"
|
||||
|
||||
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
|
||||
over `let`**.
|
||||
|
||||
$ let 'b = a' "(a += 3) + $((a = 1)), b++"
|
||||
$ echo "$a - $b - $?"
|
||||
$ let 'b = a' "(a += 3) + $((a = 1)), b++"
|
||||
$ echo "$a - $b - $?"
|
||||
4 - 2 - 0
|
||||
|
||||
Is equivalent to the [arithmetic evaluation compound
|
||||
command](/syntax/ccmd/arithmetic_eval):
|
||||
|
||||
$ (( b = a, (a += 3) + $((a = 1)), b++ ))
|
||||
$ echo "$a - $b - $?"
|
||||
$ echo "$a - $b - $?"
|
||||
4 - 2 - 0
|
||||
|
||||
\<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.
|
||||
|
||||
~ $ ( y=1+1 let x=y; declare -p x y )
|
||||
declare -- x="2"
|
||||
declare -- x="2"
|
||||
bash: declare: y: not found
|
||||
|
||||
~ $ ( y=1+1 let x=y++; declare -p x y )
|
||||
declare -- x="2"
|
||||
declare -- y="3"
|
||||
declare -- x="2"
|
||||
declare -- y="3"
|
||||
|
||||
This can be useful in certain situations where a temporary variable is
|
||||
needed.
|
||||
@ -70,7 +70,7 @@ needed.
|
||||
## Portability considerations
|
||||
|
||||
- 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:
|
||||
`# POSIX
|
||||
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
|
||||
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
|
||||
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
|
||||
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|
|
||||
|4|
|
||||
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
|
||||
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}
|
||||
prefix input1
|
||||
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
|
||||
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 input3
|
||||
prefix input5
|
||||
@ -154,7 +154,7 @@ and returns the record.
|
||||
#!/usr/bin/env bash
|
||||
|
||||
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() {
|
||||
@ -167,17 +167,17 @@ and returns the record.
|
||||
local n
|
||||
|
||||
_f
|
||||
mapfile -tc2 -C _f "$1"
|
||||
eval "$1"'=("${'"$1"'[@]##*:}")' # Return the array with some modification
|
||||
mapfile -tc2 -C _f "$1"
|
||||
eval "$1"'=("${'"$1"'[@]##*:}")' # Return the array with some modification
|
||||
}
|
||||
|
||||
main() {
|
||||
local -a keys vals
|
||||
parseRecords vals
|
||||
showRecord "$1"
|
||||
showRecord "$1"
|
||||
}
|
||||
|
||||
main "$1" <<-"EOF"
|
||||
main "$1" <<-"EOF"
|
||||
fabric.domain:123
|
||||
routex:1
|
||||
routey:2
|
||||
|
@ -41,7 +41,7 @@ formatstring may point to are given after that, here, indicated by
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
declare -a x='([0]="hi")'
|
||||
declare -a x='([0]="hi")'
|
||||
|
||||
...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
|
||||
@ -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
|
||||
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
|
||||
|
||||
@ -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
|
||||
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
|
||||
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
|
||||
printf '%3d | %04o | 0x%02x\n' "$x" "$x" "$x"
|
||||
printf '%3d | %04o | 0x%02x\n' "$x" "$x" "$x"
|
||||
done
|
||||
|
||||
### 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
|
||||
digits, ...):
|
||||
|
||||
the_mac="0:13:ce:7:7a:ad"
|
||||
the_mac="0:13:ce:7:7a:ad"
|
||||
|
||||
# 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
|
||||
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
|
||||
|
||||
@ -294,16 +294,16 @@ This code was found in Solaris manpage for echo(1).
|
||||
|
||||
Solaris version of `/usr/bin/echo` is equivalent to:
|
||||
|
||||
printf "%b\n" "$*"
|
||||
printf "%b\n" "$*"
|
||||
|
||||
Solaris `/usr/ucb/echo` is equivalent to:
|
||||
|
||||
if [ "X$1" = "X-n" ]
|
||||
if [ "X$1" = "X-n" ]
|
||||
then
|
||||
shift
|
||||
printf "%s" "$*"
|
||||
printf "%s" "$*"
|
||||
else
|
||||
printf "%s\n" "$*"
|
||||
printf "%s\n" "$*"
|
||||
fi
|
||||
|
||||
### prargs Implementation
|
||||
@ -311,14 +311,14 @@ Solaris `/usr/ucb/echo` is equivalent to:
|
||||
Working off the replacement echo, here is a terse implementation of
|
||||
prargs:
|
||||
|
||||
printf '"%b"\n' "$0" "$@" | nl -v0 -s": "
|
||||
printf '"%b"\n' "$0" "$@" | nl -v0 -s": "
|
||||
|
||||
### repeating a character (for example to print a line)
|
||||
|
||||
A small trick: Combining printf and parameter expansion to draw a line
|
||||
|
||||
length=40
|
||||
printf -v line '%*s' "$length"
|
||||
printf -v line '%*s' "$length"
|
||||
echo ${line// /-}
|
||||
|
||||
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.
|
||||
|
||||
|
||||
$ 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
|
||||
`%s
|
||||
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
|
||||
correct awk syntax.
|
||||
|
||||
$ echo "Foo" | awk '{ printf( "%s\n", $1 ) }'
|
||||
$ echo "Foo" | awk '{ printf( "%s\n", $1 ) }'
|
||||
Foo
|
||||
|
||||
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
|
||||
readability.
|
||||
|
||||
echo "Foo" | awk '{ system( "printf \"%s\\n \" \"" $1 "\"" ) }'
|
||||
echo "Foo" | awk '{ system( "printf \"%s\\n \" \"" $1 "\"" ) }'
|
||||
Foo
|
||||
|
||||
## Differences from C, and portability considerations
|
||||
@ -395,7 +395,7 @@ readability.
|
||||
modifiers specified by POSIX during format string parsing.
|
||||
|
||||
``` c|builtins/printf.def
|
||||
#define LENMODS "hjlLtz"
|
||||
#define LENMODS "hjlLtz"
|
||||
...
|
||||
/* skip possible format modifiers */
|
||||
modstart = fmt;
|
||||
@ -427,15 +427,15 @@ fmt++;
|
||||
shift
|
||||
nameref x=$1
|
||||
shift
|
||||
x=$(command printf "$@")
|
||||
x=$(command printf "$@")
|
||||
;;
|
||||
*)
|
||||
command printf "$@"
|
||||
command printf "$@"
|
||||
esac
|
||||
}
|
||||
builtin cut
|
||||
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
|
||||
# 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!
|
||||
|
||||
while read -r; do
|
||||
printf '"%s"\n' "$REPLY"
|
||||
done <<<" a line with prefix and postfix space "
|
||||
printf '"%s"\n' "$REPLY"
|
||||
done <<<" a line with prefix and postfix space "
|
||||
|
||||
\</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: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: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: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: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: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:46 * ormaaj struggles to think of a point to that...
|
||||
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() {
|
||||
while read -r; do
|
||||
printf "%s\n" "$REPLY"
|
||||
done <"$1"
|
||||
printf "%s\n" "$REPLY"
|
||||
done <"$1"
|
||||
}
|
||||
|
||||
<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() {
|
||||
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:
|
||||
@ -160,14 +160,14 @@ Notes:
|
||||
|
||||
Read can be used to split a string:
|
||||
|
||||
var="one two three"
|
||||
read -r col1 col2 col3 <<< "$var"
|
||||
printf "col1: %s col2: %s col3 %s\n" "$col1" "$col2" "$col3"
|
||||
var="one two three"
|
||||
read -r col1 col2 col3 <<< "$var"
|
||||
printf "col1: %s col2: %s col3 %s\n" "$col1" "$col2" "$col3"
|
||||
|
||||
Take care that you cannot use a pipe:
|
||||
|
||||
echo "$var" | read col1 col2 col3 # does not work!
|
||||
printf "col1: %s col2: %s col3 %s\n" "$col1" "$col2" "$col3"
|
||||
echo "$var" | read col1 col2 col3 # does not work!
|
||||
printf "col1: %s col2: %s col3 %s\n" "$col1" "$col2" "$col3"
|
||||
|
||||
Why? because the commands of the pipe run in subshells that cannot
|
||||
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
|
||||
variable get the remaining of the line:
|
||||
|
||||
read col1 col2 col3 <<< "one two three four"
|
||||
printf "%s\n" "$col3" #prints three four
|
||||
read col1 col2 col3 <<< "one two three four"
|
||||
printf "%s\n" "$col3" #prints three four
|
||||
|
||||
#### 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*
|
||||
[IFS](/syntax/shellvars#IFS), the Internal Field Separator.
|
||||
|
||||
IFS=":" read -r col1 col2 <<< "hello:world"
|
||||
printf "col1: %s col2: %s\n" "$col1" "$col2"
|
||||
IFS=":" read -r col1 col2 <<< "hello:world"
|
||||
printf "col1: %s col2: %s\n" "$col1" "$col2"
|
||||
|
||||
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
|
||||
@ -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
|
||||
(space or tab), the fields are separated by **exactly** one character:
|
||||
|
||||
IFS=":" read -r col1 col2 col3 <<< "hello::world"
|
||||
printf "col1: %s col2: %s col3 %s\n" "$col1" "$col2" "$col3"
|
||||
IFS=":" read -r col1 col2 col3 <<< "hello::world"
|
||||
printf "col1: %s col2: %s col3 %s\n" "$col1" "$col2" "$col3"
|
||||
|
||||
See how the `::` in the middle infact defines an additional *empty
|
||||
field*.
|
||||
@ -207,13 +207,13 @@ field*.
|
||||
The fields are separated by exactly one character, but the character can
|
||||
be different between each field:
|
||||
|
||||
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"
|
||||
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"
|
||||
|
||||
### Are you sure?
|
||||
|
||||
asksure() {
|
||||
echo -n "Are you sure (Y/N)? "
|
||||
echo -n "Are you sure (Y/N)? "
|
||||
while read -r -n 1 -s answer; do
|
||||
if [[ $answer = [YyNn] ]]; then
|
||||
[[ $answer = [Yy] ]] && retval=0
|
||||
@ -229,16 +229,16 @@ be different between each field:
|
||||
|
||||
### using it
|
||||
if asksure; then
|
||||
echo "Okay, performing rm -rf / then, master...."
|
||||
echo "Okay, performing rm -rf / then, master...."
|
||||
else
|
||||
echo "Pfff..."
|
||||
echo "Pfff..."
|
||||
fi
|
||||
|
||||
### Ask for a path with a default value
|
||||
|
||||
<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.
|
||||
|
||||
@ -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
|
||||
date/time string are recognized correctly.
|
||||
|
||||
datetime="2008:07:04 00:34:45"
|
||||
IFS=": " read -r year month day hour minute second <<< "$datetime"
|
||||
datetime="2008:07:04 00:34:45"
|
||||
IFS=": " read -r year month day hour minute second <<< "$datetime"
|
||||
|
||||
## Portability considerations
|
||||
|
||||
|
@ -59,7 +59,7 @@ There are no options.
|
||||
[shift_verbose](internals/shell_options#shift_verbose)
|
||||
[shopt](commands/builtin/shopt) option is enabled. Ksh93, pdksh, posh,
|
||||
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
|
||||
` In most shells, you can work around this problem using the
|
||||
[command](/commands/builtin/command) builtin to suppress fatal errors
|
||||
|
@ -63,12 +63,12 @@ Here's a demonstration of this behavior.
|
||||
# Direct recursion depth.
|
||||
# Search up the stack for the first non-FUNCNAME[1] and count how deep we are.
|
||||
callDepth() {
|
||||
# Strip "main" off the end of FUNCNAME[@] if current function is named "main" and
|
||||
# Bash added an extra "main" for non-interactive scripts.
|
||||
if [[ main == !(!("${FUNCNAME[1]}")|!("${FUNCNAME[-1]}")) && $- != *i* ]]; then
|
||||
local -a 'fnames=("${FUNCNAME[@]:1:${#FUNCNAME[@]}-2}")'
|
||||
# Strip "main" off the end of FUNCNAME[@] if current function is named "main" and
|
||||
# Bash added an extra "main" for non-interactive scripts.
|
||||
if [[ main == !(!("${FUNCNAME[1]}")|!("${FUNCNAME[-1]}")) && $- != *i* ]]; then
|
||||
local -a 'fnames=("${FUNCNAME[@]:1:${#FUNCNAME[@]}-2}")'
|
||||
else
|
||||
local -a 'fnames=("${FUNCNAME[@]:1}")'
|
||||
local -a 'fnames=("${FUNCNAME[@]:1}")'
|
||||
fi
|
||||
|
||||
if (( ! ${#fnames[@]} )); then
|
||||
@ -86,7 +86,7 @@ Here's a demonstration of this behavior.
|
||||
|
||||
# This function is the magic stack walker.
|
||||
unset2() {
|
||||
unset -v -- "$@"
|
||||
unset -v -- "$@"
|
||||
}
|
||||
|
||||
f() {
|
||||
@ -97,11 +97,11 @@ Here's a demonstration of this behavior.
|
||||
f
|
||||
else
|
||||
trap 'declare -p a' DEBUG
|
||||
unset2 a # declare -- a="5"
|
||||
unset a a # declare -- a="4"
|
||||
unset a # declare -- a="2"
|
||||
unset2 a # declare -- a="5"
|
||||
unset a a # declare -- a="4"
|
||||
unset a # declare -- a="2"
|
||||
unset a # ./unset-tests: line 44: declare: a: not found
|
||||
: # declare -- a="global scope yo"
|
||||
: # declare -- a="global scope yo"
|
||||
fi
|
||||
}
|
||||
|
||||
@ -112,11 +112,11 @@ Here's a demonstration of this behavior.
|
||||
|
||||
output:
|
||||
|
||||
declare -- a="5"
|
||||
declare -- a="4"
|
||||
declare -- a="2"
|
||||
declare -- a="5"
|
||||
declare -- a="4"
|
||||
declare -- a="2"
|
||||
./unset-tests: line 44: declare: a: not found
|
||||
declare -- a="global scope yo"
|
||||
declare -- a="global scope yo"
|
||||
|
||||
Some things to observe:
|
||||
|
||||
@ -143,15 +143,15 @@ Like several other Bash builtins that take parameter names, unset
|
||||
expands its arguments.
|
||||
|
||||
~ $ ( 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
|
||||
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]'
|
||||
+ 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
|
||||
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
|
||||
|
||||
if test -e /etc/passwd; then
|
||||
echo "Alright man..." >&2
|
||||
echo "Alright man..." >&2
|
||||
else
|
||||
echo "Yuck! Where is it??" >&2
|
||||
echo "Yuck! Where is it??" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@ -37,9 +37,9 @@ Let's rewrite the above example to use it:
|
||||
# test if /etc/passwd exists
|
||||
|
||||
if [ -e /etc/passwd ]; then
|
||||
echo "Alright man..." >&2
|
||||
echo "Alright man..." >&2
|
||||
else
|
||||
echo "Yuck! Where is it??" >&2
|
||||
echo "Yuck! Where is it??" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@ -53,12 +53,12 @@ some of your music files:
|
||||
|
||||
#!/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
|
||||
echo "Let's rock" >&2
|
||||
if [ -e "$mymusic" ]; then
|
||||
echo "Let's rock" >&2
|
||||
else
|
||||
echo "No music today, sorry..." >&2
|
||||
echo "No music today, sorry..." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@ -75,7 +75,7 @@ filenames =\> **too many 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
|
||||
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
|
||||
might encounter:
|
||||
|
||||
var=""
|
||||
if [ -n $var ]; then echo "var is not empty"; fi
|
||||
var=""
|
||||
if [ -n $var ]; then echo "var is not empty"; fi
|
||||
|
||||
This code prints "var is not empty", even though `-n something` is
|
||||
supposed to be true if `$var` is not empty - **why?**
|
||||
@ -228,8 +228,8 @@ them with the shell `&&` and `||` **list control operators**.
|
||||
|
||||
See this:
|
||||
|
||||
if [ -n "$var"] && [ -e "$var"]; then
|
||||
echo "\$var is not null and a file named $var exists!"
|
||||
if [ -n "$var"] && [ -e "$var"]; then
|
||||
echo "\$var is not null and a file named $var exists!"
|
||||
fi
|
||||
|
||||
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`
|
||||
and `-o`, thus:
|
||||
|
||||
if [ -n "$var" -a -e "$var" ] ; then
|
||||
echo "\$var is not null and a file named $var exists"
|
||||
if [ -n "$var" -a -e "$var" ] ; then
|
||||
echo "\$var is not null and a file named $var exists"
|
||||
fi
|
||||
|
||||
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 `]'
|
||||
|
||||
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:
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
|
||||
$ 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
|
||||
|
||||
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,
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
# 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
|
||||
|
||||
## 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
|
||||
(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
|
||||
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,
|
||||
at least for doing one single test.
|
||||
@ -368,7 +368,7 @@ which you may have already had yourself**:
|
||||
Hi All,
|
||||
|
||||
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?:
|
||||
|
||||
|
||||
@ -381,9 +381,9 @@ which you may have already had yourself**:
|
||||
{
|
||||
if [-d $i]
|
||||
then
|
||||
echo "$i is a directory! Yay!"
|
||||
echo "$i is a directory! Yay!"
|
||||
else
|
||||
echo "$i is not a directory!"
|
||||
echo "$i is not a directory!"
|
||||
fi
|
||||
}
|
||||
done
|
||||
@ -410,7 +410,7 @@ test command:
|
||||
> (QUOTED TEXT WAS REMOVED)
|
||||
|
||||
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
|
||||
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
|
||||
disappear entirely. This is why test arguments should always be quoted.
|
||||
|
||||
if test -d "$file"
|
||||
if [ -d "$file" ]
|
||||
if test -d "$file"
|
||||
if [ -d "$file" ]
|
||||
|
||||
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
|
||||
non-zero in text length. Because "-d" is non-zero length "test -d" 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
|
||||
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
|
||||
operator.
|
||||
|
||||
DATA="something"
|
||||
if test "$DATA" # true, $DATA is non-zero length
|
||||
DATA="something"
|
||||
if test "$DATA" # true, $DATA is non-zero length
|
||||
|
||||
DATA=""
|
||||
if test "$DATA" # false, $DATA is zero length
|
||||
DATA=""
|
||||
if test "$DATA" # false, $DATA is zero length
|
||||
|
||||
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
|
||||
only one argument is defined to be the same as test -n $DATA.
|
||||
|
||||
DATA="-d"
|
||||
if test "$DATA" # true, $DATA is non-zero length
|
||||
DATA="-d"
|
||||
if test "$DATA" # true, $DATA is non-zero length
|
||||
if test -d # true, same as previous case.
|
||||
|
||||
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:
|
||||
|
||||
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:
|
||||
|
||||
@ -556,7 +556,7 @@ entries of a directory, if an entry is a directory (`[ -d "$fn" ]`),
|
||||
print its name:
|
||||
|
||||
for fn in *; do
|
||||
[ -d "$fn" ] && echo "$fn"
|
||||
[ -d "$fn" ] && echo "$fn"
|
||||
done
|
||||
|
||||
## See also
|
||||
|
@ -63,7 +63,7 @@ The **output** is converted to **base 10** by default
|
||||
## Scale And Base
|
||||
|
||||
`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
|
||||
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
|
||||
sa # save it in register a
|
||||
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
|
||||
4laxp # same operation for 4, in one line
|
||||
EOF
|
||||
@ -221,7 +221,7 @@ we are used to reading:
|
||||
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
|
||||
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
|
||||
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() {
|
||||
if [[ $verbose ]]; then
|
||||
chatter() {
|
||||
echo "$@"
|
||||
echo "$@"
|
||||
}
|
||||
chatter "$@"
|
||||
chatter "$@"
|
||||
else
|
||||
chatter() {
|
||||
:
|
||||
@ -32,9 +32,9 @@ common example is a script that gives the user the option of having
|
||||
fi
|
||||
}
|
||||
|
||||
echo "Waiting for 10 seconds."
|
||||
echo "Waiting for 10 seconds."
|
||||
for i in {1..10}; do
|
||||
chatter "$i"
|
||||
chatter "$i"
|
||||
sleep 1
|
||||
done
|
||||
|
||||
@ -56,35 +56,35 @@ FIXME Add more examples!
|
||||
# various versions of -perm /+ blah blah and hacks
|
||||
find() {
|
||||
hash find || { echo 'find not found!'; exit 1; }
|
||||
# We can be pretty sure "$0" should be executable.
|
||||
if [[ $(command find "$0" -executable 2> /dev/null) ]]; then
|
||||
# We can be pretty sure "$0" should be executable.
|
||||
if [[ $(command find "$0" -executable 2> /dev/null) ]]; then
|
||||
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() {
|
||||
typeset arg args
|
||||
for arg do
|
||||
[[ $arg = -executable ]] && args+=(-perm /u+x) || args+=("$arg")
|
||||
[[ $arg = -executable ]] && args+=(-perm /u+x) || args+=("$arg")
|
||||
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() {
|
||||
typeset arg args
|
||||
for arg do
|
||||
[[ $arg = -executable ]] && args+=(-perm +u+x) || args+=("$arg")
|
||||
[[ $arg = -executable ]] && args+=(-perm +u+x) || args+=("$arg")
|
||||
done
|
||||
command find "${args[@]}"
|
||||
command find "${args[@]}"
|
||||
}
|
||||
else # Last resort
|
||||
find() {
|
||||
typeset arg args
|
||||
for arg do
|
||||
[[ $arg = -executable ]] && args+=(-exec test -x {} \; -print) || args+=("$arg")
|
||||
[[ $arg = -executable ]] && args+=(-exec test -x {} \; -print) || args+=("$arg")
|
||||
done
|
||||
command find "${args[@]}"
|
||||
command find "${args[@]}"
|
||||
}
|
||||
fi
|
||||
find "$@"
|
||||
find "$@"
|
||||
}
|
||||
|
||||
\<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:
|
||||
|
||||
#!/bin/bash
|
||||
echo "Reading config...." >&2
|
||||
echo "Reading config...." >&2
|
||||
source /etc/cool.cfg
|
||||
echo "Config for the username: $cool_username" >&2
|
||||
echo "Config for the target host: $cool_host" >&2
|
||||
echo "Config for the username: $cool_username" >&2
|
||||
echo "Config for the target host: $cool_host" >&2
|
||||
|
||||
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
|
||||
@ -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
|
||||
like:
|
||||
|
||||
cool_username="guest"
|
||||
cool_host="foo.example.com"
|
||||
cool_username="guest"
|
||||
cool_host="foo.example.com"
|
||||
|
||||
These are normal statements understood by Bash, nothing special. Of
|
||||
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:
|
||||
|
||||
#!/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
|
||||
echo "Config for the username: $cool_username" >&2
|
||||
echo "Config for the target host: $cool_host" >&2
|
||||
echo "Config for the username: $cool_username" >&2
|
||||
echo "Config for the target host: $cool_host" >&2
|
||||
|
||||
## 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:
|
||||
|
||||
#!/bin/bash
|
||||
echo "Reading system-wide config...." >&2
|
||||
echo "Reading system-wide config...." >&2
|
||||
. /etc/cool.cfg
|
||||
if [ -r ~/.coolrc ]; then
|
||||
echo "Reading user config...." >&2
|
||||
echo "Reading user config...." >&2
|
||||
. ~/.coolrc
|
||||
fi
|
||||
|
||||
@ -68,9 +68,9 @@ malicious code:
|
||||
username=god_only_knows
|
||||
hostname=www.example.com
|
||||
password=secret ; echo rm -rf ~/*
|
||||
parameter=foobar && echo "You've bene pwned!";
|
||||
parameter=foobar && echo "You've bene pwned!";
|
||||
# hey look, weird code follows...
|
||||
echo "I am the skull virus..."
|
||||
echo "I am the skull virus..."
|
||||
echo rm -fr ~/*
|
||||
mailto=netadmin@example.com
|
||||
|
||||
@ -88,15 +88,15 @@ filters by description:
|
||||
configfile_secured='/tmp/cool.cfg'
|
||||
|
||||
# check if the file contains something we don't want
|
||||
if egrep -q -v '^#|^[^ ]*=[^;]*' "$configfile"; then
|
||||
echo "Config file is unclean, cleaning it..." >&2
|
||||
if egrep -q -v '^#|^[^ ]*=[^;]*' "$configfile"; then
|
||||
echo "Config file is unclean, cleaning it..." >&2
|
||||
# filter the original to a new file
|
||||
egrep '^#|^[^ ]*=[^;&]*' "$configfile" > "$configfile_secured"
|
||||
configfile="$configfile_secured"
|
||||
egrep '^#|^[^ ]*=[^;&]*' "$configfile" > "$configfile_secured"
|
||||
configfile="$configfile_secured"
|
||||
fi
|
||||
|
||||
# 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
|
||||
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:
|
||||
|
||||
``` 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
|
||||
@ -49,7 +49,7 @@ In Bash we don't need the subshell or the external basename command. See
|
||||
expansion](/syntax/pe#substring_removal):
|
||||
|
||||
``` 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:
|
||||
@ -69,7 +69,7 @@ in the previous step? Well, if you don't quote `$j`, wordsplitting can
|
||||
happen again.
|
||||
|
||||
``` bash
|
||||
$ mkdir "$j" && cd "$j" && ... && cd ..
|
||||
$ mkdir "$j" && cd "$j" && ... && cd ..
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
``` 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 -,
|
||||
@ -90,17 +90,17 @@ problem.)
|
||||
So now we have:
|
||||
|
||||
``` 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 $ 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:
|
||||
|
||||
``` 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.
|
||||
@ -110,15 +110,15 @@ implementations I've seen can do it with the -d flag. So we can drop the
|
||||
cd commands entirely:
|
||||
|
||||
``` bash
|
||||
$ mkdir "$j" && unzip -d "$j" "$i"
|
||||
$ mkdir "$j" && unzip -d "$j" "$i"
|
||||
```
|
||||
|
||||
``` 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 $ 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.
|
||||
|
@ -38,10 +38,10 @@ command, `printf` ("help printf"). Shown here as an example Bash
|
||||
function to prefix text to file content:
|
||||
|
||||
|
||||
# insertHead "$text" "$file"
|
||||
# insertHead "$text" "$file"
|
||||
|
||||
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>**
|
||||
@ -221,9 +221,9 @@ prints the result to stdout - `,p`):
|
||||
To compare, here's a possible `sed` solution which must use Bash
|
||||
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
|
||||
# security risk, but well, sometimes GNU > security, you know...
|
||||
sed '${h;s/.*/cat FILE2/e;G}' FILE1
|
||||
@ -250,10 +250,10 @@ about it with the g (global) command:
|
||||
|
||||
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'
|
||||
|
||||
#replace the first line matching 1 by "replacement"
|
||||
#replace the first line matching 1 by "replacement"
|
||||
#(because it starts searching from the last line)
|
||||
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 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'
|
||||
|
||||
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
|
||||
example from an array:
|
||||
|
||||
while getopts :f:h opt "${MY_OWN_SET[@]}"; do
|
||||
while getopts :f:h opt "${MY_OWN_SET[@]}"; do
|
||||
...
|
||||
done
|
||||
|
||||
A call to `getopts` **without** these additional arguments is
|
||||
**equivalent** to explicitly calling it with `"$@"`:
|
||||
|
||||
getopts ... "$@"
|
||||
getopts ... "$@"
|
||||
|
||||
### Error Reporting
|
||||
|
||||
@ -189,13 +189,13 @@ preceding the whole option string with a colon (`:`):
|
||||
``` bash
|
||||
#!/bin/bash
|
||||
|
||||
while getopts ":a" opt; do
|
||||
while getopts ":a" opt; do
|
||||
case $opt in
|
||||
a)
|
||||
echo "-a was triggered!" >&2
|
||||
echo "-a was triggered!" >&2
|
||||
;;
|
||||
\?)
|
||||
echo "Invalid option: -$OPTARG" >&2
|
||||
echo "Invalid option: -$OPTARG" >&2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
@ -285,17 +285,17 @@ Let's extend our example from above. Just a little bit:
|
||||
``` bash
|
||||
#!/bin/bash
|
||||
|
||||
while getopts ":a:" opt; do
|
||||
while getopts ":a:" opt; do
|
||||
case $opt in
|
||||
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
|
||||
;;
|
||||
:)
|
||||
echo "Option -$OPTARG requires an argument." >&2
|
||||
echo "Option -$OPTARG requires an argument." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
@ -67,9 +67,9 @@ With mkdir it seems, we have our two steps in one simple operation. A
|
||||
|
||||
``` bash
|
||||
if mkdir /var/lock/mylock; then
|
||||
echo "Locking succeeded" >&2
|
||||
echo "Locking succeeded" >&2
|
||||
else
|
||||
echo "Lock failed - exit" >&2
|
||||
echo "Lock failed - exit" >&2
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
@ -103,12 +103,12 @@ Need to write a code example here.
|
||||
|
||||
``` bash
|
||||
|
||||
if ( set -o noclobber; echo "locked" > "$lockfile") 2> /dev/null; then
|
||||
trap 'rm -f "$lockfile"; exit $?' INT TERM EXIT
|
||||
echo "Locking succeeded" >&2
|
||||
rm -f "$lockfile"
|
||||
if ( set -o noclobber; echo "locked" > "$lockfile") 2> /dev/null; then
|
||||
trap 'rm -f "$lockfile"; exit $?' INT TERM EXIT
|
||||
echo "Locking succeeded" >&2
|
||||
rm -f "$lockfile"
|
||||
else
|
||||
echo "Lock failed - exit" >&2
|
||||
echo "Lock failed - exit" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@ -136,59 +136,59 @@ the locking process is shown:
|
||||
#!/bin/bash
|
||||
|
||||
# lock dirs/files
|
||||
LOCKDIR="/tmp/statsgen-lock"
|
||||
PIDFILE="${LOCKDIR}/PID"
|
||||
LOCKDIR="/tmp/statsgen-lock"
|
||||
PIDFILE="${LOCKDIR}/PID"
|
||||
|
||||
# exit codes and text
|
||||
ENO_SUCCESS=0; ETXT[0]="ENO_SUCCESS"
|
||||
ENO_GENERAL=1; ETXT[1]="ENO_GENERAL"
|
||||
ENO_LOCKFAIL=2; ETXT[2]="ENO_LOCKFAIL"
|
||||
ENO_RECVSIG=3; ETXT[3]="ENO_RECVSIG"
|
||||
ENO_SUCCESS=0; ETXT[0]="ENO_SUCCESS"
|
||||
ENO_GENERAL=1; ETXT[1]="ENO_GENERAL"
|
||||
ENO_LOCKFAIL=2; ETXT[2]="ENO_LOCKFAIL"
|
||||
ENO_RECVSIG=3; ETXT[3]="ENO_RECVSIG"
|
||||
|
||||
###
|
||||
### start locking attempt
|
||||
###
|
||||
|
||||
trap 'ECODE=$?; echo "[statsgen] Exit: ${ETXT[ECODE]}($ECODE)" >&2' 0
|
||||
echo -n "[statsgen] Locking: " >&2
|
||||
trap 'ECODE=$?; echo "[statsgen] Exit: ${ETXT[ECODE]}($ECODE)" >&2' 0
|
||||
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
|
||||
# storing the PID fails
|
||||
trap 'ECODE=$?;
|
||||
echo "[statsgen] Removing lock. Exit: ${ETXT[ECODE]}($ECODE)" >&2
|
||||
rm -rf "${LOCKDIR}"' 0
|
||||
echo "$$" >"${PIDFILE}"
|
||||
echo "[statsgen] Removing lock. Exit: ${ETXT[ECODE]}($ECODE)" >&2
|
||||
rm -rf "${LOCKDIR}"' 0
|
||||
echo "$$" >"${PIDFILE}"
|
||||
# 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!
|
||||
trap 'echo "[statsgen] Killed by a signal." >&2
|
||||
# the trap on "0" (EXIT) from above will be triggered by this trap's "exit" command!
|
||||
trap 'echo "[statsgen] Killed by a signal." >&2
|
||||
exit ${ENO_RECVSIG}' 1 2 3 15
|
||||
echo "success, installed signal handlers"
|
||||
echo "success, installed signal handlers"
|
||||
|
||||
else
|
||||
|
||||
# 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
|
||||
# about to remove the lock -- exit, we're *still* locked
|
||||
# Thanks to Grzegorz Wierzowiecki for pointing out this race condition on
|
||||
# http://wiki.grzegorz.wierzowiecki.pl/code:mutex-in-bash
|
||||
if [ $? != 0 ]; then
|
||||
echo "lock failed, PID ${OTHERPID} is active" >&2
|
||||
echo "lock failed, PID ${OTHERPID} is active" >&2
|
||||
exit ${ENO_LOCKFAIL}
|
||||
fi
|
||||
|
||||
if ! kill -0 $OTHERPID &>/dev/null; then
|
||||
# lock is stale, remove it and restart
|
||||
echo "removing stale lock of nonexistant PID ${OTHERPID}" >&2
|
||||
rm -rf "${LOCKDIR}"
|
||||
echo "[statsgen] restarting myself" >&2
|
||||
exec "$0" "$@"
|
||||
echo "removing stale lock of nonexistant PID ${OTHERPID}" >&2
|
||||
rm -rf "${LOCKDIR}"
|
||||
echo "[statsgen] restarting myself" >&2
|
||||
exec "$0" "$@"
|
||||
else
|
||||
# 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}
|
||||
fi
|
||||
|
||||
|
@ -183,7 +183,7 @@ give the `-v` option:
|
||||
You can extract all files, or files (not) matching specific patterns
|
||||
from an archive using constructs like:
|
||||
|
||||
# "normal" extraction
|
||||
# "normal" extraction
|
||||
pax -rf myarchive.tar '*.txt'
|
||||
|
||||
# with inverted pattern
|
||||
@ -201,8 +201,8 @@ command, 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 | gzip | ssh user@host "cd distantdir && gunzip | pax -r -v" #compress the sent data
|
||||
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
|
||||
|
||||
These commands create a copy of localdir in distandir (distantdir/dir)
|
||||
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
|
||||
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
|
||||
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
|
||||
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:
|
||||
|
||||
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:
|
||||
|
||||
@ -656,7 +656,7 @@ recommendations:
|
||||
# Good!
|
||||
{ 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.
|
||||
cmd 2>& 1 <<< stuff
|
||||
|
||||
|
@ -9,18 +9,18 @@ We have a simple **stat.sh** script:
|
||||
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [ -z "$1" ]
|
||||
if [ -z "$1" ]
|
||||
then
|
||||
DIR=./
|
||||
else
|
||||
DIR=$1
|
||||
fi
|
||||
|
||||
echo "Evaluate *.py statistics"
|
||||
echo "Evaluate *.py statistics"
|
||||
FILES=$(find $DIR -name '*.py' | wc -l)
|
||||
LINES=$((find $DIR -name '*.py' -print0 | xargs -0 cat) | wc -l)
|
||||
echo "PYTHON FILES: $FILES"
|
||||
echo "PYTHON LINES: $LINES"
|
||||
echo "PYTHON FILES: $FILES"
|
||||
echo "PYTHON LINES: $LINES"
|
||||
|
||||
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\>**
|
||||
|
@ -17,7 +17,7 @@ From `builtins/read.def`:
|
||||
``` C
|
||||
/* If there are no variables, save the text of the line read to the
|
||||
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
|
||||
enough to not do it. Without the bash behavior, there is no way
|
||||
to read a line completely without interpretation or modification
|
||||
|
@ -249,7 +249,7 @@ For this topic, see also
|
||||
| `${PARAMETER//PATTERN/REPLACEMENT}` | 2.0 | |
|
||||
| `${PARAMETER:OFFSET:LENGTH}` | 2.0 | |
|
||||
| `${!PARAMETER}` (indirection) | 2.0 | |
|
||||
| `$"..."` (localized strings) | 2.0 | |
|
||||
| `$"..."` (localized strings) | 2.0 | |
|
||||
| `$'...'` (ANSI-C-like strings) | 2.0 | |
|
||||
| `\xNNN` in `$'...'` (and `echo -e`) | 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
|
||||
#!/bin/bash
|
||||
echo "Hello world..."
|
||||
echo "Hello world..."
|
||||
```
|
||||
|
||||
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
|
||||
if grep ^root /etc/passwd; then
|
||||
echo "The user root was found"
|
||||
echo "The user root was found"
|
||||
else
|
||||
echo "The user root was not found"
|
||||
echo "The user root was not found"
|
||||
fi
|
||||
```
|
||||
|
||||
@ -176,10 +176,10 @@ brackets are not part of the shell syntax, the left bracket **is** the
|
||||
test command!
|
||||
|
||||
``` bash
|
||||
if [ "$mystring" = "Hello world" ]; then
|
||||
echo "Yeah dude, you entered the right words..."
|
||||
if [ "$mystring" = "Hello world" ]; then
|
||||
echo "Yeah dude, you entered the right words..."
|
||||
else
|
||||
echo "Eeeek - go away..."
|
||||
echo "Eeeek - go away..."
|
||||
fi
|
||||
```
|
||||
|
||||
@ -190,8 +190,8 @@ This lets you execute a command based on whether or not the previous
|
||||
command completed successfully:
|
||||
|
||||
``` bash
|
||||
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."
|
||||
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."
|
||||
```
|
||||
|
||||
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
|
||||
#!/bin/bash
|
||||
# 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
|
||||
@ -227,14 +227,14 @@ a shutdown:
|
||||
``` bash
|
||||
#!/bin/bash
|
||||
# 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
|
||||
logger -t SYSHALT "System halt requested"
|
||||
echo "System halt requested" | mail -s "System halt" netadmin@example.com
|
||||
logger -t SYSHALT "System halt requested"
|
||||
|
||||
##### The following "code block" is effectively ignored
|
||||
: <<"SOMEWORD"
|
||||
##### The following "code block" is effectively ignored
|
||||
: <<"SOMEWORD"
|
||||
/etc/init.d/mydatabase clean_stop
|
||||
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
|
||||
SOMEWORD
|
||||
##### The ignored codeblock ends here
|
||||
@ -312,13 +312,13 @@ echo $foo
|
||||
}
|
||||
|
||||
|
||||
# this will print "external"
|
||||
# this will print "external"
|
||||
echo $foo
|
||||
|
||||
# this will print "internal"
|
||||
# this will print "internal"
|
||||
printvalue
|
||||
|
||||
# this will print - again - "external"
|
||||
# this will print - again - "external"
|
||||
echo $foo
|
||||
```
|
||||
|
||||
@ -341,10 +341,10 @@ A variable can be tagged to be part of the environment using the
|
||||
``` bash
|
||||
# create a new variable and set it:
|
||||
# -> 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 it an environment variable: "export" it
|
||||
# -> Make it an environment variable: "export" it
|
||||
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`:
|
||||
|
||||
echo "DEBUG: current i=$i" >&2
|
||||
echo "DEBUG: current i=$i" >&2
|
||||
|
||||
If you read input from **anywhere**, such as a file or [command
|
||||
substitution](/syntax/expansion/cmdsubst), print the debug output with
|
||||
literal quotes, to see leading and trailing spaces!
|
||||
|
||||
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,
|
||||
which is handy for verifying whether strings are what they appear to be.
|
||||
|
||||
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
|
||||
|
||||
## 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:
|
||||
|
||||
set -x
|
||||
foo="bar baz"
|
||||
foo="bar baz"
|
||||
[ $foo = test ]
|
||||
|
||||
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...
|
||||
|
||||
# next try
|
||||
[ "$foo" = test ]
|
||||
[ "$foo" = test ]
|
||||
|
||||
`xtrace` now gives
|
||||
|
||||
@ -157,10 +157,10 @@ For general debugging purposes you can also define a function and a
|
||||
variable to use:
|
||||
|
||||
debugme() {
|
||||
[[ $script_debug = 1 ]] && "$@" || :
|
||||
[[ $script_debug = 1 ]] && "$@" || :
|
||||
# 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
|
||||
# "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)
|
||||
}
|
||||
|
||||
@ -171,9 +171,9 @@ Use it like this:
|
||||
script_debug=1
|
||||
# to turn it off, set script_debug=0
|
||||
|
||||
debugme logger "Sorting the database"
|
||||
debugme logger "Sorting the database"
|
||||
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
|
||||
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 ...
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
$ echo "Hello world!"
|
||||
bash: !": event not found
|
||||
$ echo "Hello world!"
|
||||
bash: !": event not found
|
||||
|
||||
This is not an error per se. It happens in interactive shells, when the
|
||||
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
|
||||
commandline, e.g.
|
||||
|
||||
$ foo () { echo "Hello world"; }
|
||||
$ foo () { echo "Hello world"; }
|
||||
bash: syntax error near unexpected token `('
|
||||
|
||||
you most likely have an alias defined with the same name as the function
|
||||
@ -323,7 +323,7 @@ carriage return character!):
|
||||
|
||||
#!/bin/bash^M
|
||||
^M
|
||||
echo "Hello world"^M
|
||||
echo "Hello world"^M
|
||||
...
|
||||
|
||||
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!
|
||||
|
||||
# THIS IS WRONG!
|
||||
$myvar="Hello world!"
|
||||
$myvar="Hello world!"
|
||||
|
||||
A variable name preceeded with a dollar-sign always means that the
|
||||
variable gets **expanded**. In the example above, it might expand to
|
||||
nothing (because it wasn't set), effectively resulting in...
|
||||
|
||||
="Hello world!"
|
||||
="Hello world!"
|
||||
|
||||
...which **definitely is wrong**!
|
||||
|
||||
@ -129,7 +129,7 @@ assigned value**:
|
||||
example=Hello
|
||||
|
||||
# CORRECT 2
|
||||
example=" Hello"
|
||||
example=" Hello"
|
||||
|
||||
### Expanding (using) variables
|
||||
|
||||
@ -142,7 +142,7 @@ result isn't always the same.
|
||||
|
||||
Let's define an example variable containing text with spaces:
|
||||
|
||||
example="Hello world"
|
||||
example="Hello world"
|
||||
|
||||
| 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`):
|
||||
|
||||
# WRONG!
|
||||
echo "The first character of PATH is ${$PATH:0:1}"
|
||||
echo "The first character of PATH is ${$PATH:0:1}"
|
||||
|
||||
# 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
|
||||
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
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
@ -225,14 +225,14 @@ This can be simplified to:
|
||||
|
||||
``` bash
|
||||
if ! grep ^root: /etc/passwd >/dev/null 2>&1; then
|
||||
echo "root was not found - check the pub at the corner"
|
||||
echo "root was not found - check the pub at the corner"
|
||||
fi
|
||||
```
|
||||
|
||||
Or, simpler yet:
|
||||
|
||||
``` bash
|
||||
grep ^root: /etc/passwd >/dev/null 2>&1 || echo "root was not found - check the pub at the corner"
|
||||
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
|
||||
@ -252,7 +252,7 @@ When you want to run a command (or a pipeline) and save (or print) the
|
||||
syntax:
|
||||
|
||||
$(ls -l /tmp)
|
||||
newvariable=$(printf "foo")
|
||||
newvariable=$(printf "foo")
|
||||
|
||||
When you want to use the **return value** of a command, just use the
|
||||
command, or add ( ) to run a command or pipeline in a subshell:
|
||||
|
@ -134,7 +134,7 @@ exit code, a 100% equivalent construct would be:
|
||||
...
|
||||
|
||||
# portable equivalent command
|
||||
if [ "$((MATH))" -ne 0 ]; then
|
||||
if [ "$((MATH))" -ne 0 ]; then
|
||||
...
|
||||
|
||||
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.
|
||||
|
||||
# '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)
|
||||
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
|
||||
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`:
|
||||
|
||||
if hash ls >/dev/null 2>&1; then
|
||||
echo "ls is available"
|
||||
echo "ls is available"
|
||||
fi
|
||||
|
||||
Somewhat of a mass-check:
|
||||
|
||||
for name in ls grep sed awk; do
|
||||
if ! hash "$name" >/dev/null 2>&1; then
|
||||
echo "FAIL: Missing command '$name'"
|
||||
if ! hash "$name" >/dev/null 2>&1; then
|
||||
echo "FAIL: Missing command '$name'"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
@ -257,7 +257,7 @@ For example, to check if the command `sed` is available in a location
|
||||
accessible by `PATH`:
|
||||
|
||||
if command -v sed >/dev/null 2>&1; then
|
||||
echo "sed is available"
|
||||
echo "sed is available"
|
||||
fi
|
||||
|
||||
[^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`:
|
||||
|
||||
#!/bin/bash
|
||||
echo "$0"
|
||||
echo "$0"
|
||||
|
||||
You see, `$0` is always set to the name the script is called with (`>`
|
||||
is the prompt...):
|
||||
@ -48,7 +48,7 @@ is the prompt...):
|
||||
|
||||
However, this isn't true for login shells:
|
||||
|
||||
> echo "$0"
|
||||
> echo "$0"
|
||||
-bash
|
||||
|
||||
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:
|
||||
|
||||
#!/bin/bash
|
||||
echo "Total number of arguments: $#"
|
||||
echo "Argument 1: $1"
|
||||
echo "Argument 2: $2"
|
||||
echo "Argument 3: $3"
|
||||
echo "Argument 4: $4"
|
||||
echo "Argument 5: $5"
|
||||
echo "Total number of arguments: $#"
|
||||
echo "Argument 1: $1"
|
||||
echo "Argument 2: $2"
|
||||
echo "Argument 3: $3"
|
||||
echo "Argument 4: $4"
|
||||
echo "Argument 5: $5"
|
||||
|
||||
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
|
||||
@ -111,7 +111,7 @@ argument list:
|
||||
numargs=$#
|
||||
for ((i=1 ; i <= numargs ; i++))
|
||||
do
|
||||
echo "$1"
|
||||
echo "$1"
|
||||
shift
|
||||
done
|
||||
|
||||
@ -126,7 +126,7 @@ a given wordlist. The loop uses the positional parameters as a wordlist:
|
||||
|
||||
for arg
|
||||
do
|
||||
echo "$arg"
|
||||
echo "$arg"
|
||||
done
|
||||
|
||||
<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
|
||||
expands to something, using the [test command](/commands/classictest):
|
||||
|
||||
while [ "$1" ]
|
||||
while [ "$1" ]
|
||||
do
|
||||
echo "$1"
|
||||
echo "$1"
|
||||
shift
|
||||
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
|
||||
value](/syntax/pe#use_an_alternate_value):
|
||||
|
||||
while [ "${1+defined}" ]; do
|
||||
echo "$1"
|
||||
while [ "${1+defined}" ]; do
|
||||
echo "$1"
|
||||
shift
|
||||
done
|
||||
|
||||
@ -226,7 +226,7 @@ decremented.
|
||||
|
||||
<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
|
||||
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
|
||||
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
|
||||
# $1: This is
|
||||
@ -282,9 +282,9 @@ rudimentary way to parse your arguments.
|
||||
|
||||
while :
|
||||
do
|
||||
case "$1" in
|
||||
case "$1" in
|
||||
-f | --file)
|
||||
file="$2" # You may want to check validity of $2
|
||||
file="$2" # You may want to check validity of $2
|
||||
shift 2
|
||||
;;
|
||||
-h | --help)
|
||||
@ -293,31 +293,31 @@ rudimentary way to parse your arguments.
|
||||
exit 0
|
||||
;;
|
||||
-u | --user)
|
||||
username="$2" # You may want to check validity of $2
|
||||
username="$2" # You may want to check validity of $2
|
||||
shift 2
|
||||
;;
|
||||
-v | --verbose)
|
||||
# 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:
|
||||
# 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:
|
||||
#
|
||||
# if [ "$verbose" ] ...
|
||||
# if [ "$verbose" ] ...
|
||||
#
|
||||
# You will see:
|
||||
#
|
||||
# if [ "verbose" ] ...
|
||||
# if [ "verbose" ] ...
|
||||
#
|
||||
# Instead of cryptic
|
||||
#
|
||||
# if [ "1" ] ...
|
||||
# if [ "1" ] ...
|
||||
#
|
||||
verbose="verbose"
|
||||
verbose="verbose"
|
||||
shift
|
||||
;;
|
||||
--) # End of all options
|
||||
shift
|
||||
break;
|
||||
-*)
|
||||
echo "Error: Unknown option: $1" >&2
|
||||
echo "Error: Unknown option: $1" >&2
|
||||
exit 1
|
||||
;;
|
||||
*) # No more options
|
||||
@ -346,7 +346,7 @@ options" for `ls` and doesn't change anything after it:
|
||||
while [[ $1 ]]
|
||||
do
|
||||
if ! ((eoo)); then
|
||||
case "$1" in
|
||||
case "$1" in
|
||||
-a)
|
||||
shift
|
||||
;;
|
||||
@ -354,29 +354,29 @@ options" for `ls` and doesn't change anything after it:
|
||||
shift
|
||||
;;
|
||||
-[^-]*a*|-a?*)
|
||||
options+=("${1//a}")
|
||||
options+=("${1//a}")
|
||||
shift
|
||||
;;
|
||||
--)
|
||||
eoo=1
|
||||
options+=("$1")
|
||||
options+=("$1")
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
options+=("$1")
|
||||
options+=("$1")
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
else
|
||||
options+=("$1")
|
||||
options+=("$1")
|
||||
|
||||
# Another (worse) way of doing the same thing:
|
||||
# options=("${options[@]}" "$1")
|
||||
# options=("${options[@]}" "$1")
|
||||
shift
|
||||
fi
|
||||
done
|
||||
|
||||
/bin/ls "${options[@]}"
|
||||
/bin/ls "${options[@]}"
|
||||
|
||||
### Using getopts
|
||||
|
||||
|
@ -96,7 +96,7 @@ into a variable. We run it in a loop here to count input lines:
|
||||
counter=0
|
||||
|
||||
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
|
||||
`$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
|
||||
|
||||
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
|
||||
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
|
||||
hello)
|
||||
echo "You said hello"
|
||||
echo "You said hello"
|
||||
;;
|
||||
bye)
|
||||
echo "You said bye"
|
||||
echo "You said bye"
|
||||
if foo; then
|
||||
bar
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "You said something weird..."
|
||||
echo "You said something weird..."
|
||||
;;
|
||||
esac
|
||||
|
||||
@ -172,8 +172,8 @@ loop counting variables, etc., ... (in the example: `file`)
|
||||
# the prefix 'MY_'
|
||||
MY_LOG_DIRECTORY=/var/adm/
|
||||
|
||||
for file in "$MY_LOG_DIRECTORY"/*; do
|
||||
echo "Found Logfile: $file"
|
||||
for file in "$MY_LOG_DIRECTORY"/*; do
|
||||
echo "Found Logfile: $file"
|
||||
done
|
||||
|
||||
### Variable initialization
|
||||
@ -190,7 +190,7 @@ via the environment.
|
||||
|
||||
The solution is simple and effective: **Initialize them**
|
||||
|
||||
my_input=""
|
||||
my_input=""
|
||||
my_array=()
|
||||
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
|
||||
course, e.g.
|
||||
|
||||
list="one two three"
|
||||
list="one two three"
|
||||
|
||||
# you MUST NOT quote $list here
|
||||
for word in $list; do
|
||||
@ -340,18 +340,18 @@ missing.
|
||||
|
||||
Example:
|
||||
|
||||
my_needed_commands="sed awk lsof who"
|
||||
my_needed_commands="sed awk lsof who"
|
||||
|
||||
missing_counter=0
|
||||
for needed_command in $my_needed_commands; do
|
||||
if ! hash "$needed_command" >/dev/null 2>&1; then
|
||||
printf "Command not found in PATH: %s\n" "$needed_command" >&2
|
||||
if ! hash "$needed_command" >/dev/null 2>&1; then
|
||||
printf "Command not found in PATH: %s\n" "$needed_command" >&2
|
||||
((missing_counter++))
|
||||
fi
|
||||
done
|
||||
|
||||
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
|
||||
fi
|
||||
|
||||
|
@ -218,9 +218,9 @@ done by the following commands:
|
||||
tput smcup
|
||||
clear
|
||||
|
||||
# example "application" follows...
|
||||
read -n1 -p "Press any key to continue..."
|
||||
# example "application" ends here
|
||||
# example "application" follows...
|
||||
read -n1 -p "Press any key to continue..."
|
||||
# example "application" ends here
|
||||
|
||||
# restore
|
||||
tput rmcup
|
||||
@ -281,14 +281,14 @@ switch to, to get the other 8 colors.
|
||||
|
||||
<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>
|
||||
|
||||
COL_NORM="$(tput setaf 9)"
|
||||
COL_RED="$(tput setaf 1)"
|
||||
COL_GREEN="$(tput setaf 2)"
|
||||
echo "It's ${COL_RED}red${COL_NORM} and ${COL_GREEN}green${COL_NORM} - have you seen?"
|
||||
COL_NORM="$(tput setaf 9)"
|
||||
COL_RED="$(tput setaf 1)"
|
||||
COL_GREEN="$(tput setaf 2)"
|
||||
echo "It's ${COL_RED}red${COL_NORM} and ${COL_GREEN}green${COL_NORM} - have you seen?"
|
||||
|
||||
### Misc
|
||||
|
||||
@ -303,11 +303,11 @@ switch to, to get the other 8 colors.
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
DATA[0]=" _/ _/ _/ _/ "
|
||||
DATA[1]=" _/_/_/_/_/ _/_/_/ _/_/_/ _/_/_/ _/_/_/ "
|
||||
DATA[2]=" _/ _/ _/ _/ _/ _/ _/_/ _/ _/"
|
||||
DATA[3]="_/_/_/_/_/ _/ _/ _/ _/ _/_/ _/ _/ "
|
||||
DATA[4]=" _/ _/ _/_/_/ _/_/_/ _/_/_/ _/ _/ "
|
||||
DATA[0]=" _/ _/ _/ _/ "
|
||||
DATA[1]=" _/_/_/_/_/ _/_/_/ _/_/_/ _/_/_/ _/_/_/ "
|
||||
DATA[2]=" _/ _/ _/ _/ _/ _/ _/_/ _/ _/"
|
||||
DATA[3]="_/_/_/_/_/ _/ _/ _/ _/ _/_/ _/ _/ "
|
||||
DATA[4]=" _/ _/ _/_/_/ _/_/_/ _/_/_/ _/ _/ "
|
||||
|
||||
# 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 {
|
||||
(($1==lastclr)) || printf %s "${colrs[lastclr=$1]:=$(tput setaf "$1")}"
|
||||
(($1==lastclr)) || printf %s "${colrs[lastclr=$1]:=$(tput setaf "$1")}"
|
||||
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:
|
||||
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 ))
|
||||
|
||||
## Different bases
|
||||
@ -88,7 +88,7 @@ mean:
|
||||
42
|
||||
|
||||
$ 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,
|
||||
and what numbers are and how they are built, then you don't need
|
||||
@ -111,7 +111,7 @@ named parameters, e.g.:
|
||||
string=3
|
||||
|
||||
echo $((test))
|
||||
# will output "3"!
|
||||
# will output "3"!
|
||||
|
||||
Of course, in the end, when it finally evaluates to something that is
|
||||
**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:
|
||||
|
||||
if ((0)); then
|
||||
echo "true"
|
||||
echo "true"
|
||||
else
|
||||
echo "false"
|
||||
echo "false"
|
||||
fi
|
||||
|
||||
## Operators
|
||||
@ -318,9 +318,9 @@ and all the others, including `set -e` for autoexit on error:
|
||||
MY_TEST_FLAG=0
|
||||
|
||||
if ((MY_TEST_FLAG)); then
|
||||
echo "MY_TEST_FLAG is ON"
|
||||
echo "MY_TEST_FLAG is ON"
|
||||
else
|
||||
echo "MY_TEST_FLAG is OFF"
|
||||
echo "MY_TEST_FLAG is OFF"
|
||||
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
|
||||
synonymous with referring to the array name without a subscript.
|
||||
|
||||
# "x" is an ordinary non-array parameter.
|
||||
$ x=hi; printf '%s ' "$x" "${x[0]}"; echo "${_[0]}"
|
||||
# "x" is an ordinary non-array parameter.
|
||||
$ x=hi; printf '%s ' "$x" "${x[0]}"; echo "${_[0]}"
|
||||
hi hi hi
|
||||
|
||||
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))
|
||||
|
||||
for ((i = 0; i <= max_index; i++)); do
|
||||
echo "Element $i: '${sentence[i]}'"
|
||||
echo "Element $i: '${sentence[i]}'"
|
||||
done
|
||||
|
||||
You always have to remember that, it seems newbies have problems
|
||||
@ -291,7 +291,7 @@ think you could just do
|
||||
$ echo ${#sentence[@]}
|
||||
1
|
||||
# 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'
|
||||
|
||||
Obviously that's wrong. What about
|
||||
@ -302,16 +302,16 @@ Obviously that's wrong. What about
|
||||
|
||||
$ echo ${#sentence[*]}
|
||||
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'
|
||||
|
||||
So what's the **right** way? The (slightly ugly) answer is, reuse the
|
||||
enumeration syntax:
|
||||
|
||||
$ unset sentence ; declare -a sentence=("${NAMES[@]}")
|
||||
$ unset sentence ; declare -a sentence=("${NAMES[@]}")
|
||||
$ echo ${#sentence[@]}
|
||||
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 1: 'Anna'
|
||||
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:
|
||||
|
||||
# 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
|
||||
`"${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
|
||||
usually query known index values:
|
||||
|
||||
for element in Begin Middle End "Very end"; do
|
||||
printf "%s" "${sentence[$element]}"
|
||||
for element in Begin Middle End "Very end"; do
|
||||
printf "%s" "${sentence[$element]}"
|
||||
done
|
||||
printf "\n"
|
||||
printf "\n"
|
||||
|
||||
**A nice code example:** Checking for duplicate files using an
|
||||
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;
|
||||
while read -r sum fname; do
|
||||
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
|
||||
flist[$sum]="$fname"
|
||||
flist[$sum]="$fname"
|
||||
fi
|
||||
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
|
||||
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 -ai a='([0]="6" [2]="4" [4]="7" [5]="42")'
|
||||
~ $ ( 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")'
|
||||
|
||||
`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]`,
|
||||
@ -428,29 +428,29 @@ without eval using the aforementioned special compound assignment
|
||||
expansion.
|
||||
|
||||
isSubset() {
|
||||
local -a 'xkeys=("${!'"$1"'[@]}")' 'ykeys=("${!'"$2"'[@]}")'
|
||||
set -- "${@/%/[key]}"
|
||||
local -a 'xkeys=("${!'"$1"'[@]}")' 'ykeys=("${!'"$2"'[@]}")'
|
||||
set -- "${@/%/[key]}"
|
||||
|
||||
(( ${#xkeys[@]} <= ${#ykeys[@]} )) || return 1
|
||||
|
||||
local key
|
||||
for key in "${xkeys[@]}"; do
|
||||
for key in "${xkeys[@]}"; do
|
||||
[[ ${!2+_} && ${!1} == ${!2} ]] || return 1
|
||||
done
|
||||
}
|
||||
|
||||
main() {
|
||||
# "a" is a subset of "b"
|
||||
# "a" is a subset of "b"
|
||||
local -a 'a=({0..5})' 'b=({0..10})'
|
||||
isSubset a b
|
||||
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})'
|
||||
isSubset a b
|
||||
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})'
|
||||
isSubset a b
|
||||
echo $? # false
|
||||
@ -465,7 +465,7 @@ dynamically calls a function whose name is resolved from the array.
|
||||
|
||||
callFuncs() {
|
||||
# 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.
|
||||
local x
|
||||
@ -477,23 +477,23 @@ dynamically calls a function whose name is resolved from the array.
|
||||
[foo]='([r]=f [s]=g [t]=h)'
|
||||
[bar]='([u]=i [v]=j [w]=k)'
|
||||
[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() {
|
||||
# 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
|
||||
eval "${x}${fun}"
|
||||
eval "${x}${fun}"
|
||||
done
|
||||
|
||||
callFuncs "$@"
|
||||
callFuncs "$@"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
main "$@"
|
||||
|
||||
## 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
|
||||
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.
|
||||
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.
|
||||
set -A a
|
||||
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)
|
||||
$ ksh -c 'set -sA arr -- foo bar bork baz; typeset -p arr' # Native sorting!
|
||||
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
|
||||
typeset arr[2]=baz
|
||||
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
|
||||
subscript assignment statement is expanded following the same rules as
|
||||
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
|
||||
` mksh has a similar but even worse problem in that the entire
|
||||
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
|
||||
`
|
||||
- **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.
|
||||
- **Fixed in 4.3** Indirection combined with another modifier expands
|
||||
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 cfoo>
|
||||
`
|
||||
- **Fixed in 4.3** Process substitutions are evaluated within array
|
||||
indexes. Zsh and ksh don't do this in any arithmetic context.
|
||||
`# print "moo"
|
||||
`# print "moo"
|
||||
dev=fd=1 _[1<(echo moo >&2)]=
|
||||
|
||||
# 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`:
|
||||
|
||||
if ! grep '^root:' /etc/passwd; then
|
||||
echo "No root user defined... eh?"
|
||||
echo "No root user defined... eh?"
|
||||
fi
|
||||
|
||||
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
|
||||
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
|
||||
command](basicgrammar#compound_commands) as a body. Structures like
|
||||
@ -223,11 +223,11 @@ like this:
|
||||
mycmd()
|
||||
{
|
||||
# this $1 belongs to the function!
|
||||
find / -iname "$1"
|
||||
find / -iname "$1"
|
||||
}
|
||||
|
||||
# this $1 belongs the script itself!
|
||||
mycmd "$1" # Execute command immediately after defining function
|
||||
mycmd "$1" # Execute command immediately after defining function
|
||||
|
||||
exit 0
|
||||
|
||||
@ -239,7 +239,7 @@ variables. Variables with the content "*() ....*".
|
||||
Something similar to the following works without "officially" declaring
|
||||
a function:
|
||||
|
||||
$ export testfn="() { echo test; }"
|
||||
$ export testfn="() { echo test; }"
|
||||
$ bash -c testfn
|
||||
test
|
||||
$
|
||||
@ -284,7 +284,7 @@ FIXME more...
|
||||
|
||||
<u>A (very) simple command</u>
|
||||
|
||||
echo "Hello world..."
|
||||
echo "Hello world..."
|
||||
|
||||
<u>All of the following are simple commands</u>
|
||||
|
||||
@ -292,7 +292,7 @@ FIXME more...
|
||||
|
||||
>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
|
||||
echo "Counter: $x"
|
||||
echo "Counter: $x"
|
||||
done
|
||||
|
||||
### 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**.
|
||||
|
||||
for ((x = 0 ; x <= 100 ; x += 10)); do
|
||||
echo "Counter: $x"
|
||||
echo "Counter: $x"
|
||||
done
|
||||
|
||||
### Bits analyzer
|
||||
@ -148,15 +148,15 @@ bits).
|
||||
function main {
|
||||
[[ $1 == +([0-9]) ]] || return
|
||||
typeset result
|
||||
if (( $(ksh -c 'printf %..2d $1' _ "$1") == ( result = $(toBin "$1") ) )); then
|
||||
printf '%s is %s in base 2!\n' "$1" "$result"
|
||||
if (( $(ksh -c 'printf %..2d $1' _ "$1") == ( result = $(toBin "$1") ) )); then
|
||||
printf '%s is %s in base 2!\n' "$1" "$result"
|
||||
else
|
||||
echo 'Oops, something went wrong with our calculation.' >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
main "${1:-123}"
|
||||
main "${1:-123}"
|
||||
|
||||
# vim: set fenc=utf-8 ff=unix ft=sh :
|
||||
|
||||
@ -191,7 +191,7 @@ demonstrating more complicated arithmetic expressions with multiple
|
||||
variables.
|
||||
|
||||
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
|
||||
|
||||
\<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
|
||||
done**, which means you can leave expansions unquoted without problems:
|
||||
|
||||
var="test word"
|
||||
var="test word"
|
||||
|
||||
case $var in
|
||||
...
|
||||
@ -78,7 +78,7 @@ Another one of my stupid examples...
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
echo "Unknown fruit - sure it isn't toxic?"
|
||||
echo "Unknown fruit - sure it isn't toxic?"
|
||||
esac
|
||||
|
||||
Here's a practical example showing a common pattern involving a `case`
|
||||
@ -95,13 +95,13 @@ function clk {
|
||||
|
||||
case $1 in
|
||||
low|high|default)
|
||||
printf '%s\n' "temp: $(<${base}/hwmon/hwmon0/temp1_input)C" "old profile: $(<${base}/power_profile)"
|
||||
echo "$1" >${base}/power_profile
|
||||
echo "new 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 "new profile: $(<${base}/power_profile)"
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $FUNCNAME [ low | high | default ]"
|
||||
printf '%s\n' "temp: $(<${base}/hwmon/hwmon0/temp1_input)C" "current profile: $(<${base}/power_profile)"
|
||||
echo "Usage: $FUNCNAME [ low | high | default ]"
|
||||
printf '%s\n' "temp: $(<${base}/hwmon/hwmon0/temp1_input)C" "current profile: $(<${base}/power_profile)"
|
||||
esac
|
||||
}
|
||||
```
|
||||
@ -113,25 +113,25 @@ between blocks using `;&`, and the non-short-circuiting `;;&` operator:
|
||||
#!/usr/bin/env bash
|
||||
|
||||
f() {
|
||||
local -a "$@"
|
||||
local -a "$@"
|
||||
local x
|
||||
|
||||
for x; do
|
||||
case $x in
|
||||
$1)
|
||||
local "$x"'+=(1)' ;;&
|
||||
local "$x"'+=(1)' ;;&
|
||||
$2)
|
||||
local "$x"'+=(2)' ;&
|
||||
local "$x"'+=(2)' ;&
|
||||
$3)
|
||||
local "$x"'+=(3)' ;;
|
||||
local "$x"'+=(3)' ;;
|
||||
$1|$2)
|
||||
local "$x"'+=(4)'
|
||||
local "$x"'+=(4)'
|
||||
esac
|
||||
IFS=, local -a "$x"'=("${x}: ${'"$x"'[*]}")'
|
||||
IFS=, local -a "$x"'=("${x}: ${'"$x"'[*]}")'
|
||||
done
|
||||
|
||||
for x; do
|
||||
echo "${!x}"
|
||||
echo "${!x}"
|
||||
done
|
||||
}
|
||||
|
||||
|
@ -71,8 +71,8 @@ With some array syntax (see [arrays](/syntax/arrays)) you can easily
|
||||
mass-expanding all elements):
|
||||
|
||||
``` bash
|
||||
for element in "${myarray[@]}"; do
|
||||
echo "Element: $element"
|
||||
for element in "${myarray[@]}"; do
|
||||
echo "Element: $element"
|
||||
done
|
||||
```
|
||||
|
||||
@ -80,8 +80,8 @@ Another way is to mass-expand all used indexes and access the array by
|
||||
index:
|
||||
|
||||
``` bash
|
||||
for index in "${!myarray[@]}"; do
|
||||
echo "Element[$index]: ${myarray[$index]}"
|
||||
for index in "${!myarray[@]}"; do
|
||||
echo "Element[$index]: ${myarray[$index]}"
|
||||
done
|
||||
```
|
||||
|
||||
@ -95,7 +95,7 @@ arguments to a command will be interpreted and parsed, and finally used:
|
||||
argtest() {
|
||||
n=1
|
||||
for arg; do
|
||||
echo "Argument $((n++)): \"$arg\""
|
||||
echo "Argument $((n++)): \"$arg\""
|
||||
done
|
||||
}
|
||||
```
|
||||
@ -108,16 +108,16 @@ filenames in a directory:
|
||||
|
||||
``` bash
|
||||
for fn in *; do
|
||||
if [ -h "$fn" ]; then
|
||||
echo -n "Symlink: "
|
||||
elif [ -d "$fn" ]; then
|
||||
echo -n "Dir: "
|
||||
elif [ -f "$fn" ]; then
|
||||
echo -n "File: "
|
||||
if [ -h "$fn" ]; then
|
||||
echo -n "Symlink: "
|
||||
elif [ -d "$fn" ]; then
|
||||
echo -n "Dir: "
|
||||
elif [ -f "$fn" ]; then
|
||||
echo -n "File: "
|
||||
else
|
||||
echo -n "Unknown: "
|
||||
echo -n "Unknown: "
|
||||
fi
|
||||
echo "$fn"
|
||||
echo "$fn"
|
||||
done
|
||||
```
|
||||
|
||||
|
@ -22,10 +22,10 @@ status (exit code)](/scripting/basics#exit_codes) of the list.
|
||||
The input and output **filedescriptors** are cumulative:
|
||||
|
||||
{
|
||||
echo "PASSWD follows"
|
||||
echo "PASSWD follows"
|
||||
cat /etc/passwd
|
||||
echo
|
||||
echo "GROUPS follows"
|
||||
echo "GROUPS follows"
|
||||
cat /etc/group
|
||||
} >output.txt
|
||||
|
||||
@ -34,10 +34,10 @@ definition](/syntax/basicgrammar#shell_function_definitions), though not
|
||||
the only compound command that's valid there:
|
||||
|
||||
print_help() {
|
||||
echo "Options:"
|
||||
echo "-h This help text"
|
||||
echo "-f FILE Use config file FILE"
|
||||
echo "-u USER Run as user USER"
|
||||
echo "Options:"
|
||||
echo "-h This help text"
|
||||
echo "-f FILE Use config file FILE"
|
||||
echo "-u USER Run as user USER"
|
||||
}
|
||||
|
||||
## Examples
|
||||
@ -46,10 +46,10 @@ the only compound command that's valid there:
|
||||
|
||||
try_catch() {
|
||||
{ # Try-block:
|
||||
eval "$@"
|
||||
eval "$@"
|
||||
} ||
|
||||
{ # Catch-block:
|
||||
echo "An error occurred"
|
||||
echo "An error occurred"
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
@ -15,9 +15,9 @@ etc...) are reflected in the "main shell".
|
||||
Execute a command in a different directory.
|
||||
|
||||
``` bash
|
||||
echo "$PWD"
|
||||
( cd /usr; echo "$PWD" )
|
||||
echo "$PWD" # Still in the original directory.
|
||||
echo "$PWD"
|
||||
( cd /usr; echo "$PWD" )
|
||||
echo "$PWD" # Still in the original directory.
|
||||
```
|
||||
|
||||
## Portability considerations
|
||||
|
@ -51,15 +51,15 @@ commands of the condition that succeeded.
|
||||
**Check if a specific user exists in /etc/passwd :-)**
|
||||
|
||||
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
|
||||
echo "Uh - am I a ghost?"
|
||||
echo "Uh - am I a ghost?"
|
||||
fi
|
||||
|
||||
**Mount with check**
|
||||
|
||||
if ! mount /mnt/backup >/dev/null 2>&1; then
|
||||
echo "FATAL: backup mount failed" >&2
|
||||
echo "FATAL: backup mount failed" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@ -67,7 +67,7 @@ commands of the condition that succeeded.
|
||||
|
||||
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
|
||||
|
||||
@ -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
|
||||
example above (multiple commands):
|
||||
|
||||
if echo "Hello world!" | grep -i hello >/dev/null 2>&1; then
|
||||
echo "You just said 'hello', yeah?"
|
||||
if echo "Hello world!" | grep -i hello >/dev/null 2>&1; then
|
||||
echo "You just said 'hello', yeah?"
|
||||
fi
|
||||
|
||||
## Portability considerations
|
||||
|
@ -18,8 +18,8 @@ function printSum {
|
||||
typeset -A args
|
||||
typeset name
|
||||
for name in first second; do
|
||||
[[ -t 0 ]] && printf 'Enter %s positive integer: ' "$name" >&2
|
||||
read -r ${BASH_VERSION+-e} "args[$name]"
|
||||
[[ -t 0 ]] && printf 'Enter %s positive integer: ' "$name" >&2
|
||||
read -r ${BASH_VERSION+-e} "args[$name]"
|
||||
[[ ${args[$name]} == +([[:digit:]]) ]] || return 1 # Validation is extremely important whenever user input is used in arithmetic.
|
||||
done
|
||||
printf 'The sum is %d.' $((${args[first]} + ${args[second]}))
|
||||
@ -49,7 +49,7 @@ x=1
|
||||
|
||||
echo $((x)) # Good.
|
||||
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]})) # Ok. Nested expansion again.
|
||||
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,
|
||||
another method is possible using [declaration
|
||||
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
|
||||
the values of \$a and \$b. Both the exact quoting, and explicitly
|
||||
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
|
||||
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:
|
||||
|
||||
<!-- -->
|
||||
|
||||
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.
|
||||
|
||||
@ -218,18 +218,18 @@ any number of arguments.
|
||||
function braceify {
|
||||
[[ $1 == +([[:digit:]]) ]] || return
|
||||
typeset -a a
|
||||
read -ra a < <(factor "$1")
|
||||
eval "echo $(printf '{$(printf ,%%.s {1..%s})}' "${a[@]:1}")"
|
||||
read -ra a < <(factor "$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
|
||||
inject that output into a template which displays the most terse brace
|
||||
expansion code that would expand `"$arg"` 1,000,000 times if evaluated.
|
||||
In this case, the output is:
|
||||
|
||||
eval printf "$arg"{,,}{,,}{,,}{,,}{,,}{,,}{,,,,,}{,,,,,}{,,,,,}{,,,,,}{,,,,,}{,,,,,}
|
||||
eval printf "$arg"{,,}{,,}{,,}{,,}{,,}{,,}{,,,,,}{,,,,,}{,,,,,}{,,,,,}{,,,,,}{,,,,,}
|
||||
|
||||
\</div\>
|
||||
|
||||
|
@ -73,7 +73,7 @@ substitution, while every `$()`-construct opens an own, subsequent
|
||||
parsing step. Everything inside `$()` is interpreted as if written
|
||||
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>**
|
||||
|
||||
@ -91,10 +91,10 @@ substitution step and echo prints "ls" and ")":
|
||||
It seems that every closing ")" confuses this construct. Also a (very
|
||||
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:
|
||||
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>**
|
||||
|
||||
@ -110,19 +110,19 @@ In general, the `$()` should be the preferred method:
|
||||
|
||||
**To get the date:**
|
||||
|
||||
DATE="$(date)"
|
||||
DATE="$(date)"
|
||||
|
||||
**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`
|
||||
target, because command substitution only catches `STDOUT`!
|
||||
|
||||
**Catch stdout and preserve trailing newlines:**
|
||||
|
||||
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 "$var"; # $var == ""
|
||||
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
|
||||
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
|
||||
`*.log`):
|
||||
|
||||
grep "changes:" *.log
|
||||
grep "changes:" *.log
|
||||
|
||||
The base syntax for the pathname expansion is the [pattern
|
||||
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>
|
||||
removed:
|
||||
|
||||
$ echo "Textfiles here:" *.txt
|
||||
$ echo "Textfiles here:" *.txt
|
||||
Textfiles here: *.txt
|
||||
|
||||
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 filename in *.txt; do
|
||||
echo "=== BEGIN: $filename ==="
|
||||
cat "$filename"
|
||||
echo "=== END: $filename ==="
|
||||
echo "=== BEGIN: $filename ==="
|
||||
cat "$filename"
|
||||
echo "=== END: $filename ==="
|
||||
done
|
||||
|
||||
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:
|
||||
|
||||
$ shopt -s nullglob
|
||||
$ echo "Textfiles here:" *.txt
|
||||
$ echo "Textfiles here:" *.txt
|
||||
Textfiles here:
|
||||
|
||||
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:
|
||||
|
||||
mystring="Hello world"
|
||||
echo "$mystring"
|
||||
mystring="Hello world"
|
||||
echo "$mystring"
|
||||
|
||||
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
|
||||
|
@ -71,7 +71,7 @@ file `/dev/fd/63`.
|
||||
Consider the following:
|
||||
|
||||
``` 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
|
||||
@ -102,7 +102,7 @@ find /etc -print0 | while IFS= read -rd '' _; do
|
||||
((counter++))
|
||||
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
|
||||
@ -121,7 +121,7 @@ while IFS= read -rN1 _; do
|
||||
((counter++))
|
||||
done < <(find /etc -printf ' ')
|
||||
|
||||
echo "$counter files"
|
||||
echo "$counter files"
|
||||
```
|
||||
|
||||
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
|
||||
f() {
|
||||
cat "$1" >"$x"
|
||||
cat "$1" >"$x"
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
``` bash
|
||||
# print "moo"
|
||||
# print "moo"
|
||||
dev=fd=1 _[1<(echo moo >&2)]=
|
||||
# fork bomb
|
||||
${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...
|
||||
|
||||
~/"my directory"
|
||||
~/"my directory"
|
||||
|
||||
...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,
|
||||
which holds the currect working directory:
|
||||
|
||||
echo "CWD is $PWD"
|
||||
echo "CWD is $PWD"
|
||||
|
||||
is equivalent to (note it **must** be a separate word!):
|
||||
|
||||
echo "CWD is" ~+
|
||||
echo "CWD is" ~+
|
||||
|
||||
## Previous working directory
|
||||
|
||||
|
@ -93,7 +93,7 @@ The behavior regarding the variable assignment errors can be tested:
|
||||
echo PRE
|
||||
|
||||
# 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; }
|
||||
|
||||
echo POST
|
||||
|
@ -35,13 +35,13 @@ Some examples:
|
||||
# redirecting stderr in the pipe
|
||||
$ coproc { ls thisfiledoesntexist; read; } 2>&1
|
||||
[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
|
||||
```
|
||||
|
||||
``` bash
|
||||
#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
|
||||
$ echo bar >&${mycoproc[1]}
|
||||
$ foobar
|
||||
@ -64,7 +64,7 @@ In Ksh you would do:
|
||||
``` bash
|
||||
# ksh93 or mksh/pdksh derivatives
|
||||
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:
|
||||
@ -73,7 +73,7 @@ In bash:
|
||||
#DOESN'T WORK
|
||||
$ coproc ls
|
||||
[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
|
||||
[1]+ Done coproc COPROC ls
|
||||
```
|
||||
@ -95,7 +95,7 @@ with `sed`:
|
||||
$ coproc sed s/^/foo/
|
||||
[1] 22981
|
||||
$ 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
|
||||
```
|
||||
|
||||
@ -116,9 +116,9 @@ continuously read the output of a coprocess and `echo` the result:
|
||||
|
||||
``` bash
|
||||
#NOT WORKING
|
||||
$ coproc awk '{print "foo" $0;fflush()}'
|
||||
$ coproc awk '{print "foo" $0;fflush()}'
|
||||
[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
|
||||
bash: line 243: read: 61: invalid file descriptor: Bad file descriptor
|
||||
```
|
||||
@ -132,10 +132,10 @@ A possible workaround:
|
||||
#WARNING: for illustration purpose ONLY
|
||||
# this is not the way to make the coprocess print its output
|
||||
# to stdout, see the redirections above.
|
||||
$ coproc awk '{print "foo" $0;fflush()}'
|
||||
$ coproc awk '{print "foo" $0;fflush()}'
|
||||
[2] 23109
|
||||
$ 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
|
||||
$ echo bar >&${COPROC[1]}
|
||||
$ foobar
|
||||
@ -152,7 +152,7 @@ assigns FDs to a default array named `COPROC` if no `NAME` is supplied.
|
||||
Here's an example:
|
||||
|
||||
``` bash
|
||||
$ coproc awk '{print "foo" $0;fflush()}'
|
||||
$ coproc awk '{print "foo" $0;fflush()}'
|
||||
[1] 22978
|
||||
```
|
||||
|
||||
@ -167,7 +167,7 @@ $ echo bar >&${COPROC[1]}
|
||||
And then read its output:
|
||||
|
||||
``` bash
|
||||
$ IFS= read -ru ${COPROC[0]} x; printf '%s\n' "$x"
|
||||
$ IFS= read -ru ${COPROC[0]} x; printf '%s\n' "$x"
|
||||
foobar
|
||||
```
|
||||
|
||||
@ -175,7 +175,7 @@ When we don't need our command anymore, we can kill it via its pid:
|
||||
|
||||
$ kill $COPROC_PID
|
||||
$
|
||||
[1]+ Terminated coproc COPROC awk '{print "foo" $0;fflush()}'
|
||||
[1]+ Terminated coproc COPROC awk '{print "foo" $0;fflush()}'
|
||||
|
||||
### 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.
|
||||
|
||||
``` bash
|
||||
$ coproc mycoproc { awk '{print "foo" $0;fflush()}' ;}
|
||||
$ coproc mycoproc { awk '{print "foo" $0;fflush()}' ;}
|
||||
[1] 23058
|
||||
$ 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
|
||||
$ 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
|
||||
|
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
|
||||
word with a trailing "s":
|
||||
|
||||
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 $WORDs"
|
||||
echo "The plural of $WORD is most likely ${WORD}s"
|
||||
|
||||
<u>Why does the first one fail?</u> It prints nothing, because a
|
||||
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
|
||||
positional parameters (arguments to a script) beyond `$9`:
|
||||
|
||||
echo "Argument 1 is: $1"
|
||||
echo "Argument 10 is: ${10}"
|
||||
echo "Argument 1 is: $1"
|
||||
echo "Argument 10 is: ${10}"
|
||||
|
||||
### 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
|
||||
|
||||
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:
|
||||
|
||||
# set some fake positional parameters
|
||||
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 ${!#}
|
||||
|
||||
You can think of this mechanism as being roughly equivalent to taking
|
||||
any parameter expansion that begins with the parameter name, and
|
||||
substituting the `!PARAMETER` part with the value of PARAMETER.
|
||||
|
||||
echo "${!var^^}"
|
||||
echo "${!var^^}"
|
||||
# ...is equivalent to
|
||||
eval 'echo "${'"$var"'^^}"'
|
||||
eval 'echo "${'"$var"'^^}"'
|
||||
|
||||
It was an unfortunate design decision to use the `!` prefix for
|
||||
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>
|
||||
|
||||
for file in *.txt; do
|
||||
mv "$file" "${file,,}"
|
||||
mv "$file" "${file,,}"
|
||||
done
|
||||
|
||||
<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''
|
||||
|
||||
## 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*):
|
||||
|
||||
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
|
||||
|
||||
@ -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
|
||||
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
|
||||
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.
|
||||
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}`...
|
||||
|
||||
@ -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
|
||||
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>`
|
||||
@ -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
|
||||
when the parameter was **unset**, not when it was empty.
|
||||
|
||||
echo "Your home directory is: ${HOME:-/home/$USER}."
|
||||
echo "${HOME:-/home/$USER} will be used to store your personal data."
|
||||
echo "Your home directory is: ${HOME:-/home/$USER}."
|
||||
echo "${HOME:-/home/$USER} will be used to store your personal data."
|
||||
|
||||
If `HOME` is unset or empty, everytime you want to print something
|
||||
useful, you need to put that parameter syntax in.
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
read -p "Enter your gender (just press ENTER to not tell us): " GENDER
|
||||
echo "Your gender is ${GENDER:-a secret}."
|
||||
read -p "Enter your gender (just press ENTER to not tell us): " GENDER
|
||||
echo "Your gender is ${GENDER:-a secret}."
|
||||
|
||||
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
|
||||
@ -650,14 +650,14 @@ Example code (please try the example cases yourself):
|
||||
|
||||
|
||||
### 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}
|
||||
|
||||
|
||||
### 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}
|
||||
@ -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
|
||||
assigned when the parameter was **unset**.
|
||||
|
||||
echo "Your home directory is: ${HOME:=/home/$USER}."
|
||||
echo "$HOME will be used to store your personal data."
|
||||
echo "Your home directory is: ${HOME:=/home/$USER}."
|
||||
echo "$HOME will be used to store your personal data."
|
||||
|
||||
After the first expansion here (`${HOME:=/home/$USER}`), `HOME` is set
|
||||
and usable.
|
||||
@ -685,9 +685,9 @@ Let's change our code example from above:
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
read -p "Enter your gender (just press ENTER to not tell us): " GENDER
|
||||
echo "Your gender is ${GENDER:=a secret}."
|
||||
echo "Ah, in case you forgot, your gender is really: $GENDER"
|
||||
read -p "Enter your gender (just press ENTER to not tell us): " GENDER
|
||||
echo "Your gender is ${GENDER:=a secret}."
|
||||
echo "Ah, in case you forgot, your gender is really: $GENDER"
|
||||
|
||||
### 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
|
||||
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
|
||||
it could influence the startup behaviour of that imaginary application).
|
||||
@ -718,8 +718,8 @@ flags:
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
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)}."
|
||||
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)}."
|
||||
|
||||
If you omit the colon, as shown in the second form
|
||||
(`${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:
|
||||
|
||||
# unset foo
|
||||
# foo=""
|
||||
# foo="something"
|
||||
# foo=""
|
||||
# foo="something"
|
||||
|
||||
if [[ ${foo+isset} = isset ]]; then
|
||||
echo "foo is set..."
|
||||
echo "foo is set..."
|
||||
else
|
||||
echo "foo is not set..."
|
||||
echo "foo is not set..."
|
||||
fi
|
||||
|
||||
### 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
|
||||
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
|
||||
|
||||
After printing this message,
|
||||
@ -785,13 +785,13 @@ are taken into account.
|
||||
|
||||
Removing the first 6 characters from a text string:
|
||||
|
||||
STRING="Hello world"
|
||||
STRING="Hello world"
|
||||
|
||||
# only print 'Hello'
|
||||
echo "${STRING%??????}"
|
||||
echo "${STRING%??????}"
|
||||
|
||||
# only print 'world'
|
||||
echo "${STRING#??????}"
|
||||
echo "${STRING#??????}"
|
||||
|
||||
# store it into the same variable
|
||||
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
|
||||
argument. As a workaround, each expansion needs to be quoted
|
||||
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>
|
||||
`
|
||||
|
||||
@ -823,14 +823,14 @@ Removing the first 6 characters from a text string:
|
||||
unquoted, this should be a rare issue. **Always quote
|
||||
them**!`touch x 'y z'
|
||||
for sh in bb {{d,b}a,{m,}k,z}sh; do
|
||||
echo "$sh"
|
||||
"$sh" -s a 'b c' d \* </dev/fd/0
|
||||
echo "$sh"
|
||||
"$sh" -s a 'b c' d \* </dev/fd/0
|
||||
done <<\EOF
|
||||
${ZSH_VERSION+:} false && emulate sh
|
||||
IFS=
|
||||
printf '<%s> ' $*
|
||||
echo
|
||||
printf "<%s> " $@
|
||||
printf "<%s> " $@
|
||||
echo
|
||||
EOF
|
||||
``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 middle of expansion through
|
||||
side-effects.`for sh in bb {{d,b}a,po,{m,}k,z}sh; do
|
||||
printf '%-4s: ' "$sh"
|
||||
"$sh" </dev/fd/0
|
||||
printf '%-4s: ' "$sh"
|
||||
"$sh" </dev/fd/0
|
||||
done <<\EOF
|
||||
${ZSH_VERSION+:} false && emulate sh
|
||||
set -f -- a b c
|
||||
unset -v IFS
|
||||
printf '<%s> ' ${*}${IFS=}${*}${IFS:=-}"${*}"
|
||||
printf '<%s> ' ${*}${IFS=}${*}${IFS:=-}"${*}"
|
||||
echo
|
||||
EOF
|
||||
``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
|
||||
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.
|
||||
` $ 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=(); 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}"'
|
||||
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
|
||||
$ 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
|
||||
$ 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
|
||||
`
|
||||
|
||||
@ -935,12 +935,12 @@ Removing the first 6 characters from a text string:
|
||||
# Bash
|
||||
$ typeset -a a=(meh bleh blerg) b
|
||||
$ 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>
|
||||
$ 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>
|
||||
$ 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>
|
||||
|
||||
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
|
||||
$ typeset -a a=(meh bleh blerg) b
|
||||
$ 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>
|
||||
$ 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>
|
||||
|
||||
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 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
|
||||
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:
|
||||
|
||||
# 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>
|
||||
|
||||
# ...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>
|
||||
|
||||
# UNLESS! we unquote the outer expansion allowing the inner quotes to
|
||||
# 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>
|
||||
|
||||
# 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>
|
||||
|
||||
# ...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:
|
||||
|
||||
$ 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>
|
||||
|
||||
$ 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>
|
||||
|
||||
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:
|
||||
|
||||
### NO NO NO: this passes three strings:
|
||||
### (1) "my
|
||||
### (1) "my
|
||||
### (2) multiword
|
||||
### (3) argument"
|
||||
MYARG="\"my multiword argument\""
|
||||
### (3) argument"
|
||||
MYARG="\"my multiword argument\""
|
||||
somecommand $MYARG
|
||||
|
||||
### THIS IS NOT (!) THE SAME AS ###
|
||||
command "my multiword argument"
|
||||
command "my multiword argument"
|
||||
|
||||
### YOU NEED ###
|
||||
MYARG="my multiword argument"
|
||||
command "$MYARG"
|
||||
MYARG="my multiword argument"
|
||||
command "$MYARG"
|
||||
|
||||
## 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
|
||||
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
|
||||
anymore
|
||||
@ -81,12 +81,12 @@ Inside a weak-quoted string there's **no special interpretion of**:
|
||||
Everything else, especially [parameter expansion](/syntax/pe), is
|
||||
performed!
|
||||
|
||||
ls -l "*"
|
||||
ls -l "*"
|
||||
|
||||
Will not be expanded. `ls` gets the literal `*` as argument. It will,
|
||||
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)
|
||||
quoted.
|
||||
@ -126,7 +126,7 @@ to get the character "`'`" as literal text:
|
||||
echo 'Here'\''s my test...'
|
||||
|
||||
# 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
|
||||
|
||||
@ -174,7 +174,7 @@ compatibility" features).
|
||||
|
||||
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
|
||||
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
|
||||
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:
|
||||
|
||||
for animal in "$mylist"; do
|
||||
for animal in "$mylist"; do
|
||||
echo $animal
|
||||
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
|
||||
string with spaces:
|
||||
|
||||
mystring="my string"
|
||||
mystring="my string"
|
||||
|
||||
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:
|
||||
|
||||
[ "$mystring" = testword ] # RIGHT!
|
||||
[ "$mystring" = testword ] # RIGHT!
|
||||
|
||||
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:
|
||||
|
||||
# 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
|
||||
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:
|
||||
|
||||
cat <<"EOF"
|
||||
cat <<"EOF"
|
||||
This won't be expanded: $PATH
|
||||
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`
|
||||
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
|
||||
rest will be given as normal parameters.
|
||||
|
@ -1238,7 +1238,7 @@ current mailfile.
|
||||
|
||||
Example content:
|
||||
|
||||
/var/mail/bfox?"You have mail":~/shell-mail?"$_ has mail!"
|
||||
/var/mail/bfox?"You have mail":~/shell-mail?"$_ has mail!"
|
||||
|
||||
### 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,
|
||||
both cause Bash to not treat spaces as special characters:
|
||||
|
||||
$ echo "Hello little world"
|
||||
$ echo "Hello little world"
|
||||
Hello little world
|
||||
|
||||
$ echo 'Hello little world'
|
||||
@ -80,7 +80,7 @@ spaces in them:
|
||||
$ cat test\ file
|
||||
m00!
|
||||
|
||||
$ cat "test file"
|
||||
$ cat "test file"
|
||||
m00!
|
||||
|
||||
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:
|
||||
|
||||
MYFILE="test file"
|
||||
MYFILE="test file"
|
||||
|
||||
When this variable is used, its occurance will be replaced by its
|
||||
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
|
||||
word splitting:
|
||||
|
||||
$ cat "$MYFILE"
|
||||
$ cat "$MYFILE"
|
||||
m00!
|
||||
|
||||
## Example
|
||||
@ -124,7 +124,7 @@ word splitting:
|
||||
Let's follow an unquoted command through these steps, assuming that the
|
||||
variable is set:
|
||||
|
||||
MYFILE="THE FILE.TXT"
|
||||
MYFILE="THE FILE.TXT"
|
||||
|
||||
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:
|
||||
|
||||
echo The file is named "$MYFILE"
|
||||
echo The file is named "$MYFILE"
|
||||
|
||||
| Word splitting after substitution (quoted!): | | | | | |
|
||||
|----------------------------------------------|--------|--------|--------|---------|----------------|
|
||||
|
Loading…
Reference in New Issue
Block a user