OSDN Git Service

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