背景
偶尔翻了之前的 bash 编程的书, 一些语法和细节发现可能还需要再 review 下, 然后就翻在线的 ABS(Advanced Bash Scripting) http://tldp.org/LDP/abs/html/index.html , 发现这篇文档还是太长了, 而且英文的东西, 当时看了明白了, 后面忘记了再去查, 就有点需要花点时间找找在哪里看过. 而且记得 10 年前好像看过一个 word 版本的 bash 指南, 写的相当简练, 现在也忘记名字了, 所以想想还是弄个简表, 第一个是自己总结, 第二个方便以后查找. 我本来想直接用这个 Learn X in Y https://learnxinyminutes.com/docs/zh-cn/bash-cn/ , 因为他的格式比较符合我想记录的方式, 但是内容上感觉有些地方没有说全, 而且有些地方有错误的写法. 我想我就以这篇为基础, 然后扩充修改下这个 bash 教程吧, 我同时也参考了其他的 Bash 15 分钟教程之类的内容, 以免遗漏.
在线练习
如果你需要一个在线的比较好的练习环境来学习 bash, 这里有个好地方 https://www.shiyanlou.com/courses/944 还有之前老版本 https://www.shiyanlou.com/courses/5 , 通过老版本里的评论和回答你可以诊断下自己所学是否足够真材实料. 后面或者会根据类似的问题列表, 整理出来一个 FAQ, 相信这样对 BASH 编程理解会更深入
内容
- #!/bin/bash
- # 脚本的第一行叫 shebang, 用来告知系统如何执行该脚本:
- # 参见: http://en.wikipedia.org/wiki/Shebang_(Unix)
- # 如你所见, 注释以 # 开头, shebang 也是注释.
- # 单行注释
- echo "A comment will follow" # 语句后的注释
- # 注释前面有空格也 OK
- echo "# this is string start with shebang" # 在引号中不起作用
- ehco '# this is anothing string start with shebang' # '#'在引号中不起作用
- echo the \# string # 不在引号中的'#'要转义
- # 每一句指令以换行或分号隔开:
- echo 'This is the first line'; echo 'This is the second line'
- # 声明一个变量:
- Variable="Some string"
- # 下面是错误的做法:
- Variable = "Some string"
- # Bash 会把 Variable 当做一个指令, 由于找不到该指令, 因此这里会报错.
- # 也不可以这样:
- Variable= 'Some string'
- # Bash 会认为'Some string' 是一条指令, 由于找不到该指令, 这里再次报错.
- # (这个例子中'Variable=' 这部分会被当作仅对'Some string' 起作用的赋值.)
- # 使用变量:
- echo $Variable
- echo "$Variable"
- echo '$Variable'
- # 当你赋值 (assign) , 导出 (export), 或者以其他方式使用变量时, 变量名前不加 $.
- # 如果要使用变量的值, 则要加 $.
- # 注意: ' (单引号) 不会展开变量 (即会屏蔽掉变量).
- # 变量加双引号和不加双引号的区别
- para="a b c d"
- ls $para # 使用 a,b,c,d 四个参数执行 ls 相当于 ls a b c d
- ls "$para" # 使用一个参数执行 ls , 相当于 ls "a b c d"
- ## 双括号结构中可以使用类似 C 语言的 cond?result-if-true:result-if-false
- (( var = 1>2?3:4))
- : # 冒号为空指令文件, 返回为 0
- # 单花括号中的? 可以用来检测变量是否被设置
- # ${var?error_info}, 可以用来做必要参数检查
- : ${var?}
- : ${var?test message}
- # 空指令版本的注释
- : <<MULTILINECOMMENT
- coomandd
- \sds
- ~2@*
- MULTILINECOMMENT
- # 扩展单引号中被转义的字符串为 ACSII
- echo $'\n\n\n'
- # 变量赋值, 使用命令执行结果, 等同于 echo `cat /etc/hostname`
- echo $(cat /etc/hostname)
- # 变量扩展或者置换
- ${param} # 等同 $param, 特定情况下这种 ${param} 的更严格的书写模式才工作
- ${param:-default} # 建议这种, 当 param 被 decare 但是没有赋值的时候, 该表达式工作良好
- ${param:=default} # If parameter not set, set it to default
- ${param:+default} # If parameter set ,use alt, else use null
- ${param?error_mesage} # If param not set ,error message
- ${#var} # var string length,if var is array ,return the first
- ${var#pattern} # 从 var 中前面移除最短的 pattern 匹配
- ${var##pattern} # 从 var 中前面移除最长的 pattern 匹配
- ${var%pattern} # 从 var 中后面移除最短的 pattern 匹配
- ${var%%pattern} # 从 var 中前面移除最长的 pattern 匹配
- # 变量截取
- ${var:pos} # 从 var 的 pos 位置开始截取, 直到最后
- ${var:pos:len} # 从 var 的 pos 位置开始截 len 长度
- ${var/pattern/replace} # 从 var 中以 replace 替换第一个 Pattern 匹配
- ${var//pattern//replace} # 从 var 中以 replace 替换所有 Pattern 匹配
- ${var/#pattern/replace} # 如果 var 的开头匹配 pattern, 拿 replace 替换第一个匹配
- ${var/%pattern/replace} # 如果 var 的结尾匹配 pattern, 拿 replace 替换第一个匹配
- ${!varprefix*} # 获取所有以 prefix 开头的变量 同 ${!varprefix@}
- # 内置变量:
- # 下面的内置变量很有用
- echo "Last program return value: $?"
- echo "Script's PID: $$"echo"Number of arguments: $#"echo"Scripts arguments: $@"echo"Scripts arguments separated in different variables: $1 $2...": <<SPECIALVARS"$*"# 所有命名参数"$@"# TODO"$?"# 命令, 脚本的退出状态"$!"# 最后一个后台执行的任务的 PID"$_"# 上一个命令的最后一个内容参数, 如果命令没给任何参数, 返回命令本身"$$" # 脚本 PID
- $# # 脚本传入参数数量
- $@ # 脚本传入的所有参数
- ${!#} # 脚本最后一个传入参数
- $1_ # 位置参数变量后跟下划线, 可以防止参数没有输入的情况
- SPECIALVARS
- # 读取输入:
- echo "What's your name?"
- read Name # 这里不需要声明新变量
- echo Hello, $Name!
- # 根据上一个指令执行结果决定是否执行下一个指令
- echo "Always executed" || echo "Only executed if first command fails"
- echo "Always executed" && echo "Only executed if first command does NOT fail"
- # 数值比较单方括号内
- a=2
- [ 1 -eq $a ] && echo true || echo false # false
- [ 1 -ne $a ] && echo true || echo false # true
- [ 1 -gt $a ] && echo true || echo false # false
- [ 1 -ge $a ] && echo true || echo false # false
- [ 1 -lt $a ] && echo true || echo false # true
- [ 1 -le $a ] && echo true || echo false # true
- # 数值比较双括号内
- (( 1 < "$a" )) && echo true || echo false # true
- (( 1> "$a" )) && echo true || echo false # false
- (( 1 <= "$a" )) && echo true || echo false # true
- (( 1>= "$a" )) && echo true || echo false # false
- # 字符串比较
- s="A"
- [ "s" == "$s" ] && echo true || echo false # false
- [ "s" != "$s" ] && echo true || echo false # true
- [ "a"> "$s" ] && echo true || echo false # true 比较 ascii 小 a 大于大 A
- [ "X" <"$s" ] && echo true || echo false # true
- [ -n "$s" ] && echo "string is not null" || echo "string is null" # string is not null
- [ -z "$s" ] && echo "string is null" ||echo "string is not null" # string is not null
- # 文件 test
- [ -x /bin/bash ] && echo "bash is executable"
- # 其他的 test 可以用 help test 列举.
- # 通常的 if 结构看起来像这样:
- # 'man test' 可查看更多的信息, 由于 $USER 为字符串, 所以我们需要用!= 这些字符串比较符号
- if [ $Name != $USER ]
- then
- echo "Your name isn't your username"
- else
- echo "Your name is your username"
- fi
- # 在 if 语句中使用 && 和 || 需要多对方括号
- if [ $Name == "Steve" ] && [ $Age -eq 15 ]
- # 也可以写成 (注意双方括号中的 &&) if [[ $Name == "Steve" && $Age -eq 15 ]]
- # 也可以写成 (注意单方括号中的 - a) if [ $Name == "Steve" -a $Age -eq 15 ]
- then
- echo "This will run if $Name is Steve AND $Age is 15."
- fi
- if [ $Name == "Daniya" ] || [ $Name == "Zach" ]
- # 也可以写成 (注意双方括号中的 ||) if [[ $Name == "Daniya" || $Name == "Zach" ]]
- # 也可以写成 (注意单方括号中的 - o) if [ $Name == "Daniya" -o $Name == "Zach" ]
- then
- echo "This will run if $Name is Daniya OR Zach."
- fi
- # 花括号扩展
- touch a{1,2,3} # 会在当前目录创建 a1,a2,a3
- touch {a,b,c}{1,2,3} # 会创建 a1,a2,a3,b1,b2,b3,c1,c2,c3
- echo {a..z} # 从 a 扩展到 z .a,b,c,d,e....x,y,z
- echo {1..9} # 从 1 至 9
- # Inline group, 或者可以成为匿名函数
- { a=123; } ; echo $a # 花括号中的变量可以在脚本后续代码中使用, 不像 function 中的变量, 出了 function 的作用域, function 里面的变量无法在外部使用
- # 数学计算, 有三种方式
- z=1
- z=`expr $z + 3` # backtricks
- echo $z
- z=$((z+3)) # 双括号
- echo $z
- let "z += 3" # let
- echo $z
- # 与其他编程语言不同的是, bash 运行时依赖上下文. 比如, 使用 ls 时, 列出当前目录.
- ls
- # 指令可以带有选项:
- ls -l # 列出文件和目录的详细信息
- # 前一个指令的输出可以当作后一个指令的输入. grep 用来匹配字符串.
- # 用下面的指令列出当前目录下所有的 txt 文件:
- ls -l | grep "\.txt"
- # 重定向输入和输出 (标准输入, 标准输出, 标准错误).
- # 以 ^EOF$ 作为结束标记从标准输入读取数据并覆盖 hello.py :
- # EOF 这种方式为 HERE String
- cat> hello.py <<EOF
- #!/usr/bin/env python
- from __future__ import print_function
- import sys
- print("#stdout", file=sys.stdout)
- print("#stderr", file=sys.stderr)
- for line in sys.stdin:
- print(line, file=sys.stdout)
- EOF
- # HereString 前面带 - 号, 可以抑制文档内部的开头 tab, 注意不是 space
- cat <<-ENDOFMESSAGE
This is line 1 of the message.
This is line 2 of the message.
This is line 3 of the message.
This is line 4 of the message.
This is the last line of the message.
- ENDOFMESSAGE
- # 重定向可以到输出, 输入和错误输出.
- python hello.py < "input.in"
- python hello.py> "output.out"
- python hello.py 2> "error.err"
- python hello.py> "output-and-error.log" 2>&1
- python hello.py> /dev/null 2>&1
- #> 会覆盖已存在的文件,>> 会以累加的方式输出文件中.
- python hello.py>> "output.out" 2>> "error.err"
- # 覆盖 output.out , 追加 error.err 并统计行数
- info bash 'Basic Shell Features' 'Redirections'> output.out 2>> error.err
- wc -l output.out error.err
- # 以 "#helloworld" 覆盖 output.out:
- cat> output.out <(echo "#helloworld")
- echo "#helloworld"> output.out
- echo "#helloworld" | cat> output.out
- echo "#helloworld" | tee output.out>/dev/null
- # 清理临时文件并显示详情 (增加 '-i' 选项启用交互模式)
- rm -v output.out error.err output-and-error.log
- # Bash 的 case 语句与 Java 和 C++ 中的 switch 语句类似: 注意结尾的双;; 是为了转义;
- case "$Variable" in
- # 列出需要匹配的字符串
- [[:upper:]]) echo "The letter is upper.";;
- [[:lower:]]) echo "The letter is lower.";;
- [0-9]) echo "It's a number";;
- *) echo "may be special letter";;
- esac
- # 循环遍历给定的参数序列:
- for Variable in {1..3}
- # 或 for Variable in "A" "B" "C"
- # 或 for Variable in `seq 2 6`
- # 或 for Variable in $(ls)
- # 或 for variable in *.sh; # *.sh 在 bash 中会扩展成本目录下所有. sh 结尾的文件
- do
- echo "$Variable"
- done
- # 或传统的 "for 循环" :
- for ((a=1; a <= 3; a++))
- do
- echo $a
- done
- # while 循环:
- while [ true ]
- do
- echo "loop body here..."
- break # break 可以跳出整个循环 # continue , 可以跳过该次循环
- done
- # Util 循环
- until false
- do
- echo "loop body here..."
- done
- # Seletc 实现菜单选择
- select vegetable in "A" "B" "C" "D"
- do
- echo "your fav is $vegetable"
- echo "Yuck!"
- echo
- break
- done
- # 你也可以使用函数
- # 定义函数:
- function foo ()
- {
- echo "Arguments work just like script arguments: $@"
- echo "And: $1 $2..."
- echo "This is a function"
- return 0
- }
- # 更简单的方法
- bar ()
- {
- echo "Another way to declare functions!"
- return 0
- }
- # 调用函数
- foo "My name is" $Name
- # 正则表达式 (在双方括号中使用)
- t="abc123"
- [[ "$t" == abc* ]] # true (globbing 比较)
- [[ "$t" == "abc*" ]] # false (字面比较)
- [[ "$t" =~ [abc]+[123]+ ]] # true (正则表达式比较)
- [[ "$t" =~ "abc*" ]] # false (字面比较)
- # 数组操作
- base64_charset=( {A..Z} {a..z} {0..9} + / = )
- echo $base64_charset # 只会输出数组的第一个项目的值
- echo ${base64_charset[*]} # 输出 A-Z a-z 0-9 + / =
- echo ${base64_charset[0]} # 输出 A
- echo ${#base64_charset[*]} # 输出数组长度 65
- echo ${base64_charset[*]:1:2} # 数组分片, 输出 B C
- base64_charset[2]="O" # 对指定项目赋值, 或者修改值
- echo ${!base64_charset[*]} # 对指定输出所有数组索引
- # for 遍历数组
- for i in ${base64_charset[*]}
- do
- echo $i
- done
- for i in ${!base64_charset[*]}
- do
- echo ${base64_charset[$i]}
- done
- # 有很多有用的指令需要学习:
- # 打印 file.txt 的最后 10 行
- tail -n 10 file.txt
- # 打印 file.txt 的前 10 行
- head -n 10 file.txt
- # 将 file.txt 按行排序
- sort file.txt
- # 报告或忽略重复的行, 用选项 -d 打印重复的行
- uniq -d file.txt
- # 打印每行中 ',' 之前内容
- cut -d ',' -f 1 file.txt
- # 将 file.txt 文件所有'okay' 替换为'great', (兼容正则表达式)
- sed -i 's/okay/great/g' file.txt
- # 将 file.txt 中匹配正则的行打印到标准输出
- # 这里打印以 "foo" 开头, "bar" 结尾的行
- grep "^foo.*bar$" file.txt
- # 使用选项 "-c" 统计行数
- grep -c "^foo.*bar$" file.txt
- # 如果只是要按字面形式搜索字符串而不是按正则表达式, 使用 fgrep (或 grep -F)
- fgrep "^foo.*bar$" file.txt
- # 以 bash 内建的'help' 指令阅读 Bash 自带文档:
- help
- help help
- help for
- help return
- help source
help .
- # 用 man 指令阅读相关的 Bash 手册
- apropos bash
- man 1 bash
- man bash
- # 用 info 指令查阅命令的 info 文档 (info 中按 ? 显示帮助信息)
- apropos info | grep '^info.*('
- man info
- info info
- info 5 info
- # 阅读 Bash 的 info 文档:
- info bash
- info bash 'Bash Features'
- info bash 6
- info --apropos bash
- # Bash 调试
- set -u # Treat unset variables as an error when substituting 对未设置值的变量报告错误
- set -e # 如果命令的执行返回不为 0 则退出
- set -n # 执行语法检查而不要运行脚本, 等同于 bash -n script.sh
- set -v # 输出每个命令, 在执行每个命令之前, 等同于 bash -v script.sh
- set -x # 和 - v 类似, 但是输出时, 会在每个命令前添加 +, 这样可以快速区分出命令和输出
- # trap , 在接收到指定信号后, 执行特定 action
- # 信号可以 trap -l 列出
- trap 'echo script exit' EXIT # 在脚本退出时, 打印上面内容
- trap 'rm -f $TEMPFILE; exit $USER_INTERRUPT' TERM INT # 在按 CTRL+C 时, 执行清理任务
来源: http://blog.51cto.com/yoke88/2125119