OSDN Git Service

b06e44e3ddc3219865027a1412539c944452c1ff
[twpd/master.git] / bash.md
1 ---
2 title: Bash scripting
3 category: CLI
4 layout: 2017/sheet
5 tags: [Featured]
6 updated: 2019-10-02
7 keywords:
8   - Variables
9   - Functions
10   - Interpolation
11   - Brace expansions
12   - Loops
13   - Conditional execution
14   - Command substitution
15 ---
16
17 Getting started
18 ---------------
19 {: .-three-column}
20
21 ### Example
22
23 ```bash
24 #!/usr/bin/env bash
25
26 NAME="John"
27 echo "Hello $NAME!"
28 ```
29
30 ### Variables
31
32 ```bash
33 NAME="John"
34 echo $NAME
35 echo "$NAME"
36 echo "${NAME}!"
37 ```
38
39 ### String quotes
40
41 ```bash
42 NAME="John"
43 echo "Hi $NAME"  #=> Hi John
44 echo 'Hi $NAME'  #=> Hi $NAME
45 ```
46
47 ### Shell execution
48
49 ```bash
50 echo "I'm in $(pwd)"
51 echo "I'm in `pwd`"
52 # Same
53 ```
54
55 See [Command substitution](http://wiki.bash-hackers.org/syntax/expansion/cmdsubst)
56
57 ### Conditional execution
58
59 ```bash
60 git commit && git push
61 git commit || echo "Commit failed"
62 ```
63
64 ### Functions
65 {: id='functions-example'}
66
67 ```bash
68 get_name() {
69   echo "John"
70 }
71
72 echo "You are $(get_name)"
73 ```
74
75 See: [Functions](#functions)
76
77 ### Conditionals
78 {: id='conditionals-example'}
79
80 ```bash
81 if [[ -z "$string" ]]; then
82   echo "String is empty"
83 elif [[ -n "$string" ]]; then
84   echo "String is not empty"
85 fi
86 ```
87
88 See: [Conditionals](#conditionals)
89
90 ### Strict mode
91
92 ```bash
93 set -euo pipefail
94 IFS=$'\n\t'
95 ```
96
97 See: [Unofficial bash strict mode](http://redsymbol.net/articles/unofficial-bash-strict-mode/)
98
99 ### Brace expansion
100
101 ```bash
102 echo {A,B}.js
103 ```
104
105 | `{A,B}` | Same as `A B` |
106 | `{A,B}.js` | Same as `A.js B.js` |
107 | `{1..5}` | Same as `1 2 3 4 5` |
108
109 See: [Brace expansion](http://wiki.bash-hackers.org/syntax/expansion/brace)
110
111
112 Parameter expansions
113 --------------------
114 {: .-three-column}
115
116 ### Basics
117
118 ```bash
119 name="John"
120 echo ${name}
121 echo ${name/J/j}    #=> "john" (substitution)
122 echo ${name:0:2}    #=> "Jo" (slicing)
123 echo ${name::2}     #=> "Jo" (slicing)
124 echo ${name::-1}    #=> "Joh" (slicing)
125 echo ${name:(-1)}   #=> "n" (slicing from right)
126 echo ${name:(-2):1} #=> "h" (slicing from right)
127 echo ${food:-Cake}  #=> $food or "Cake"
128 ```
129
130 ```bash
131 length=2
132 echo ${name:0:length}  #=> "Jo"
133 ```
134
135 See: [Parameter expansion](http://wiki.bash-hackers.org/syntax/pe)
136
137 ```bash
138 STR="/path/to/foo.cpp"
139 echo ${STR%.cpp}    # /path/to/foo
140 echo ${STR%.cpp}.o  # /path/to/foo.o
141 echo ${STR%/*}      # /path/to
142
143 echo ${STR##*.}     # cpp (extension)
144 echo ${STR##*/}     # foo.cpp (basepath)
145
146 echo ${STR#*/}      # path/to/foo.cpp
147 echo ${STR##*/}     # foo.cpp
148
149 echo ${STR/foo/bar} # /path/to/bar.cpp
150 ```
151
152 ```bash
153 STR="Hello world"
154 echo ${STR:6:5}   # "world"
155 echo ${STR:-5:5}  # "world"
156 ```
157
158 ```bash
159 SRC="/path/to/foo.cpp"
160 BASE=${SRC##*/}   #=> "foo.cpp" (basepath)
161 DIR=${SRC%$BASE}  #=> "/path/to/" (dirpath)
162 ```
163
164 ### Substitution
165
166 | Code | Description |
167 | --- | --- |
168 | `${FOO%suffix}` | Remove suffix |
169 | `${FOO#prefix}` | Remove prefix |
170 | --- | --- |
171 | `${FOO%%suffix}` | Remove long suffix |
172 | `${FOO##prefix}` | Remove long prefix |
173 | --- | --- |
174 | `${FOO/from/to}` | Replace first match |
175 | `${FOO//from/to}` | Replace all |
176 | --- | --- |
177 | `${FOO/%from/to}` | Replace suffix |
178 | `${FOO/#from/to}` | Replace prefix |
179
180 ### Comments
181
182 ```bash
183 # Single line comment
184 ```
185
186 ```bash
187 : '
188 This is a
189 multi line
190 comment
191 '
192 ```
193
194 ### Substrings
195
196 | Expression      | Description                    |
197 | --------------- | ------------------------------ |
198 | `${FOO:0:3}`    | Substring _(position, length)_ |
199 | `${FOO:(-3):3}` | Substring from the right       |
200
201 ### Length
202
203 | Expression | Description      |
204 | ---------- | ---------------- |
205 | `${#FOO}`  | Length of `$FOO` |
206
207 ### Manipulation
208
209 ```bash
210 STR="HELLO WORLD!"
211 echo ${STR,}   #=> "hELLO WORLD!" (lowercase 1st letter)
212 echo ${STR,,}  #=> "hello world!" (all lowercase)
213
214 STR="hello world!"
215 echo ${STR^}   #=> "Hello world!" (uppercase 1st letter)
216 echo ${STR^^}  #=> "HELLO WORLD!" (all uppercase)
217 ```
218
219 ### Default values
220
221 | Expression        | Description                                              |
222 | ----------------- | -------------------------------------------------------- |
223 | `${FOO:-val}`     | `$FOO`, or `val` if unset (or null)                      |
224 | `${FOO:=val}`     | Set `$FOO` to `val` if unset (or null)                   |
225 | `${FOO:+val}`     | `val` if `$FOO` is set (and not null)                    |
226 | `${FOO:?message}` | Show error message and exit if `$FOO` is unset (or null) |
227
228 Omitting the `:` removes the (non)nullity checks, e.g. `${FOO-val}` expands to `val` if unset otherwise `$FOO`.
229
230 Loops
231 -----
232 {: .-three-column}
233
234 ### Basic for loop
235
236 ```bash
237 for i in /etc/rc.*; do
238   echo $i
239 done
240 ```
241
242 ### C-like for loop
243
244 ```bash
245 for ((i = 0 ; i < 100 ; i++)); do
246   echo $i
247 done
248 ```
249
250 ### Ranges
251
252 ```bash
253 for i in {1..5}; do
254     echo "Welcome $i"
255 done
256 ```
257
258 #### With step size
259
260 ```bash
261 for i in {5..50..5}; do
262     echo "Welcome $i"
263 done
264 ```
265
266 ### Reading lines
267
268 ```bash
269 cat file.txt | while read line; do
270   echo $line
271 done
272 ```
273
274 ### Forever
275
276 ```bash
277 while true; do
278   ยทยทยท
279 done
280 ```
281
282 Functions
283 ---------
284 {: .-three-column}
285
286 ### Defining functions
287
288 ```bash
289 myfunc() {
290     echo "hello $1"
291 }
292 ```
293
294 ```bash
295 # Same as above (alternate syntax)
296 function myfunc() {
297     echo "hello $1"
298 }
299 ```
300
301 ```bash
302 myfunc "John"
303 ```
304
305 ### Returning values
306
307 ```bash
308 myfunc() {
309     local myresult='some value'
310     echo $myresult
311 }
312 ```
313
314 ```bash
315 result="$(myfunc)"
316 ```
317
318 ### Raising errors
319
320 ```bash
321 myfunc() {
322   return 1
323 }
324 ```
325
326 ```bash
327 if myfunc; then
328   echo "success"
329 else
330   echo "failure"
331 fi
332 ```
333
334 ### Arguments
335
336 | Expression | Description                            |
337 | ---        | ---                                    |
338 | `$#`       | Number of arguments                    |
339 | `$*`       | All arguments                          |
340 | `$@`       | All arguments, starting from first     |
341 | `$1`       | First argument                         |
342 | `$_`       | Last argument of the previous command  |
343
344 See [Special parameters](http://wiki.bash-hackers.org/syntax/shellvars#special_parameters_and_shell_variables).
345
346 Conditionals
347 ------------
348 {: .-three-column}
349
350 ### Conditions
351
352 Note that `[[` is actually a command/program that returns either `0` (true) or `1` (false). Any program that obeys the same logic (like all base utils, such as `grep(1)` or `ping(1)`) can be used as condition, see examples.
353
354 | Condition                | Description           |
355 | ---                      | ---                   |
356 | `[[ -z STRING ]]`        | Empty string          |
357 | `[[ -n STRING ]]`        | Not empty string      |
358 | `[[ STRING == STRING ]]` | Equal                 |
359 | `[[ STRING != STRING ]]` | Not Equal             |
360 | ---                      | ---                   |
361 | `[[ NUM -eq NUM ]]`      | Equal                 |
362 | `[[ NUM -ne NUM ]]`      | Not equal             |
363 | `[[ NUM -lt NUM ]]`      | Less than             |
364 | `[[ NUM -le NUM ]]`      | Less than or equal    |
365 | `[[ NUM -gt NUM ]]`      | Greater than          |
366 | `[[ NUM -ge NUM ]]`      | Greater than or equal |
367 | ---                      | ---                   |
368 | `[[ STRING =~ STRING ]]` | Regexp                |
369 | ---                      | ---                   |
370 | `(( NUM < NUM ))`        | Numeric conditions    |
371
372 #### More conditions
373
374 | Condition            | Description              |
375 | -------------------- | ------------------------ |
376 | `[[ -o noclobber ]]` | If OPTIONNAME is enabled |
377 | ---                  | ---                      |
378 | `[[ ! EXPR ]]`       | Not                      |
379 | `[[ X && Y ]]`       | And                      |
380 | `[[ X || Y ]]`       | Or                       |
381
382 ### File conditions
383
384 | Condition               | Description             |
385 | ---                     | ---                     |
386 | `[[ -e FILE ]]`         | Exists                  |
387 | `[[ -r FILE ]]`         | Readable                |
388 | `[[ -h FILE ]]`         | Symlink                 |
389 | `[[ -d FILE ]]`         | Directory               |
390 | `[[ -w FILE ]]`         | Writable                |
391 | `[[ -s FILE ]]`         | Size is > 0 bytes       |
392 | `[[ -f FILE ]]`         | File                    |
393 | `[[ -x FILE ]]`         | Executable              |
394 | ---                     | ---                     |
395 | `[[ FILE1 -nt FILE2 ]]` | 1 is more recent than 2 |
396 | `[[ FILE1 -ot FILE2 ]]` | 2 is more recent than 1 |
397 | `[[ FILE1 -ef FILE2 ]]` | Same files              |
398
399 ### Example
400
401 ```bash
402 # String
403 if [[ -z "$string" ]]; then
404   echo "String is empty"
405 elif [[ -n "$string" ]]; then
406   echo "String is not empty"
407 else
408   echo "This never happens"
409 fi
410 ```
411
412 ```bash
413 # Combinations
414 if [[ X && Y ]]; then
415   ...
416 fi
417 ```
418
419 ```bash
420 # Equal
421 if [[ "$A" == "$B" ]]
422 ```
423
424 ```bash
425 # Regex
426 if [[ "A" =~ . ]]
427 ```
428
429 ```bash
430 if (( $a < $b )); then
431    echo "$a is smaller than $b"
432 fi
433 ```
434
435 ```bash
436 if [[ -e "file.txt" ]]; then
437   echo "file exists"
438 fi
439 ```
440
441 Arrays
442 ------
443
444 ### Defining arrays
445
446 ```bash
447 Fruits=('Apple' 'Banana' 'Orange')
448 ```
449
450 ```bash
451 Fruits[0]="Apple"
452 Fruits[1]="Banana"
453 Fruits[2]="Orange"
454 ```
455
456 ### Working with arrays
457
458 ```bash
459 echo ${Fruits[0]}           # Element #0
460 echo ${Fruits[-1]}          # Last element
461 echo ${Fruits[@]}           # All elements, space-separated
462 echo ${#Fruits[@]}          # Number of elements
463 echo ${#Fruits}             # String length of the 1st element
464 echo ${#Fruits[3]}          # String length of the Nth element
465 echo ${Fruits[@]:3:2}       # Range (from position 3, length 2)
466 echo ${!Fruits[@]}          # Keys of all elements, space-separated
467 ```
468
469 ### Operations
470
471 ```bash
472 Fruits=("${Fruits[@]}" "Watermelon")    # Push
473 Fruits+=('Watermelon')                  # Also Push
474 Fruits=( ${Fruits[@]/Ap*/} )            # Remove by regex match
475 unset Fruits[2]                         # Remove one item
476 Fruits=("${Fruits[@]}")                 # Duplicate
477 Fruits=("${Fruits[@]}" "${Veggies[@]}") # Concatenate
478 lines=(`cat "logfile"`)                 # Read from file
479 ```
480
481 ### Iteration
482
483 ```bash
484 for i in "${arrayName[@]}"; do
485   echo $i
486 done
487 ```
488
489 Dictionaries
490 ------------
491 {: .-three-column}
492
493 ### Defining
494
495 ```bash
496 declare -A sounds
497 ```
498
499 ```bash
500 sounds[dog]="bark"
501 sounds[cow]="moo"
502 sounds[bird]="tweet"
503 sounds[wolf]="howl"
504 ```
505
506 Declares `sound` as a Dictionary object (aka associative array).
507
508 ### Working with dictionaries
509
510 ```bash
511 echo ${sounds[dog]} # Dog's sound
512 echo ${sounds[@]}   # All values
513 echo ${!sounds[@]}  # All keys
514 echo ${#sounds[@]}  # Number of elements
515 unset sounds[dog]   # Delete dog
516 ```
517
518 ### Iteration
519
520 #### Iterate over values
521
522 ```bash
523 for val in "${sounds[@]}"; do
524   echo $val
525 done
526 ```
527
528 #### Iterate over keys
529
530 ```bash
531 for key in "${!sounds[@]}"; do
532   echo $key
533 done
534 ```
535
536 Options
537 -------
538
539 ### Options
540
541 ```bash
542 set -o noclobber  # Avoid overlay files (echo "hi" > foo)
543 set -o errexit    # Used to exit upon error, avoiding cascading errors
544 set -o pipefail   # Unveils hidden failures
545 set -o nounset    # Exposes unset variables
546 ```
547
548 ### Glob options
549
550 ```bash
551 shopt -s nullglob    # Non-matching globs are removed  ('*.foo' => '')
552 shopt -s failglob    # Non-matching globs throw errors
553 shopt -s nocaseglob  # Case insensitive globs
554 shopt -s dotglob     # Wildcards match dotfiles ("*.sh" => ".foo.sh")
555 shopt -s globstar    # Allow ** for recursive matches ('lib/**/*.rb' => 'lib/a/b/c.rb')
556 ```
557
558 Set `GLOBIGNORE` as a colon-separated list of patterns to be removed from glob
559 matches.
560
561 History
562 -------
563
564 ### Commands
565
566 | `history` | Show history |
567 | `shopt -s histverify` | Don't execute expanded result immediately |
568
569 ### Expansions
570
571 | `!$` | Expand last parameter of most recent command |
572 | `!*` | Expand all parameters of most recent command |
573 | `!-n` | Expand `n`th most recent command |
574 | `!n` | Expand `n`th command in history |
575 | `!<command>` | Expand most recent invocation of command `<command>` |
576
577 ### Operations
578
579 | Code                 | Description                                                           |
580 | -------------------- | --------------------------------------------------------------------- |
581 | `!!`                 | Execute last command again                                            |
582 | `!!:s/<FROM>/<TO>/`  | Replace first occurrence of `<FROM>` to `<TO>` in most recent command |
583 | `!!:gs/<FROM>/<TO>/` | Replace all occurrences of `<FROM>` to `<TO>` in most recent command  |
584 | `!$:t`               | Expand only basename from last parameter of most recent command       |
585 | `!$:h`               | Expand only directory from last parameter of most recent command      |
586
587 `!!` and `!$` can be replaced with any valid expansion.
588
589 ### Slices
590
591 | Code     | Description                                                                              |
592 | -------- | ---------------------------------------------------------------------------------------- |
593 | `!!:n`   | Expand only `n`th token from most recent command (command is `0`; first argument is `1`) |
594 | `!^`     | Expand first argument from most recent command                                           |
595 | `!$`     | Expand last token from most recent command                                               |
596 | `!!:n-m` | Expand range of tokens from most recent command                                          |
597 | `!!:n-$` | Expand `n`th token to last from most recent command                                      |
598
599 `!!` can be replaced with any valid expansion i.e. `!cat`, `!-2`, `!42`, etc.
600
601
602 Miscellaneous
603 -------------
604
605 ### Numeric calculations
606
607 ```bash
608 $((a + 200))      # Add 200 to $a
609 ```
610
611 ```bash
612 $((RANDOM%=200))  # Random number 0..200
613 ```
614
615 ### Subshells
616
617 ```bash
618 (cd somedir; echo "I'm now in $PWD")
619 pwd # still in first directory
620 ```
621
622 ### Redirection
623
624 ```bash
625 python hello.py > output.txt   # stdout to (file)
626 python hello.py >> output.txt  # stdout to (file), append
627 python hello.py 2> error.log   # stderr to (file)
628 python hello.py 2>&1           # stderr to stdout
629 python hello.py 2>/dev/null    # stderr to (null)
630 python hello.py &>/dev/null    # stdout and stderr to (null)
631 ```
632
633 ```bash
634 python hello.py < foo.txt      # feed foo.txt to stdin for python
635 ```
636
637 ### Inspecting commands
638
639 ```bash
640 command -V cd
641 #=> "cd is a function/alias/whatever"
642 ```
643
644 ### Trap errors
645
646 ```bash
647 trap 'echo Error at about $LINENO' ERR
648 ```
649
650 or
651
652 ```bash
653 traperr() {
654   echo "ERROR: ${BASH_SOURCE[1]} at about ${BASH_LINENO[0]}"
655 }
656
657 set -o errtrace
658 trap traperr ERR
659 ```
660
661 ### Case/switch
662
663 ```bash
664 case "$1" in
665   start | up)
666     vagrant up
667     ;;
668
669   *)
670     echo "Usage: $0 {start|stop|ssh}"
671     ;;
672 esac
673 ```
674
675 ### Source relative
676
677 ```bash
678 source "${0%/*}/../share/foo.sh"
679 ```
680
681 ### printf
682
683 ```bash
684 printf "Hello %s, I'm %s" Sven Olga
685 #=> "Hello Sven, I'm Olga
686
687 printf "1 + 1 = %d" 2
688 #=> "1 + 1 = 2"
689
690 printf "This is how you print a float: %f" 2
691 #=> "This is how you print a float: 2.000000"
692 ```
693
694 ### Directory of script
695
696 ```bash
697 DIR="${0%/*}"
698 ```
699
700 ### Getting options
701
702 ```bash
703 while [[ "$1" =~ ^- && ! "$1" == "--" ]]; do case $1 in
704   -V | --version )
705     echo $version
706     exit
707     ;;
708   -s | --string )
709     shift; string=$1
710     ;;
711   -f | --flag )
712     flag=1
713     ;;
714 esac; shift; done
715 if [[ "$1" == '--' ]]; then shift; fi
716 ```
717
718 ### Heredoc
719
720 ```sh
721 cat <<END
722 hello world
723 END
724 ```
725
726 ### Reading input
727
728 ```bash
729 echo -n "Proceed? [y/n]: "
730 read ans
731 echo $ans
732 ```
733
734 ```bash
735 read -n 1 ans    # Just one character
736 ```
737
738 ### Special variables
739
740 | `$?` | Exit status of last task |
741 | `$!` | PID of last background task |
742 | `$$` | PID of shell |
743 | `$0` | Filename of the shell script |
744
745 See [Special parameters](http://wiki.bash-hackers.org/syntax/shellvars#special_parameters_and_shell_variables).
746
747 ### Go to previous directory
748
749 ```bash
750 pwd # /home/user/foo
751 cd bar/
752 pwd # /home/user/foo/bar
753 cd -
754 pwd # /home/user/foo
755 ```
756
757 ### Check for command's result
758
759 ```bash
760 if ping -c 1 google.com; then
761   echo "It appears you have a working internet connection"
762 fi
763 ```
764
765 ### Grep check
766
767 ```bash
768 if grep -q 'foo' ~/.bash_history; then
769   echo "You appear to have typed 'foo' in the past"
770 fi
771 ```
772
773 ## Also see
774 {: .-one-column}
775
776 * [Bash-hackers wiki](http://wiki.bash-hackers.org/) _(bash-hackers.org)_
777 * [Shell vars](http://wiki.bash-hackers.org/syntax/shellvars) _(bash-hackers.org)_
778 * [Learn bash in y minutes](https://learnxinyminutes.com/docs/bash/) _(learnxinyminutes.com)_
779 * [Bash Guide](http://mywiki.wooledge.org/BashGuide) _(mywiki.wooledge.org)_
780 * [ShellCheck](https://www.shellcheck.net/) _(shellcheck.net)_