Bash小结
最近项目脚本又开始重构,写了不少Bash脚本,索性写小结归纳下;
脚本并不难
我自己写脚本是早前有个项目是用脚本构建的,就把那个几百行的脚本翻了一下,边看边查,看他做了什么?是怎么一步步构建项目的?后面自己写项目就开始写自己项目的脚本了。我说脚本不难是因为:
- 我自己命令行用的相对比较熟,自己也写过一些命令;知道敲一条命令大概做了什么(
fork
、exec
、wait
) - 脚本没有什么抽象能力:不像其它编程语言,有自定义数据类型、接口、多线程这些。脚本就只有一些简单的表达式:
if-else
、for
、case
、function
,然后就没有了 - 脚本有些不太好理解的地方是:还有些单个字符的选项(命令选项之类)、还有很多特殊的符号;对于前者单个选项字符的很多都是单词缩写;如果命令行用的很熟,会解决一部分(用过了),剩下的边学边查
- 很多时候,不是脚本难不难的问题,是你对脚本中使用的命令熟不熟的问题
- 最后一个是思维层面的:我们不用把所有的弄懂了在去开始写代码,就像我们不必学完所有的汉字才开始交流和写文章,learning by doing,边学边做
Hello world
bash
#!/bin/bash
echo "hello bash"
在通常的shell脚本中,井号#
用作注释行。但这里的第一行是个例外,#
后面的惊叹号会告诉shell用哪个shell来运行脚本,它也有特定的语法和称呼,叫Shebang。语法:
bash
#!interpreter [optional-arg]
Shebang工作机制:在类Unix系统上,程序加载器会分析#!
后面的内容,并将它做为解释器,在将该文件路径作为该解释器的参数。类似下面这样:
bash
$ bash hello.sh
解释类语言都是可以这样运行,比如python的:python hello.py
,js的:node hello.js
。
运行Hello world
:
bash
# 方式1
$ bash hello.sh
# 方式2:加可执行权限,运行
$ chmod u+x hello.sh
$ ./hello.sh
变量
bash
# 定义
NAME="John"
# 取值
echo $NAME
echo "$NAME"
echo "${NAME}"
echo "Hi $NAME!"
命令替换
bash
# $()`格式
echo "I'm in $(pwd)"
# 反引号字符(`)
echo "I'm in `pwd`"
WORKING_DIR=$(pwd)
WORKING_DIR=`pwd`
echo "WORKING_DIR: ${WORKING_DIR}"
if-else分支
bash
if [[ -z "$string" ]]; then
echo "String is empty"
elif [[ -n "$string" ]]; then
echo "String is not empty"
fi
Case选择
bash
case "$1" in
start | up)
vagrant up
;;
*)
echo "Usage: $0 {start|stop|ssh}"
;;
esac
循环
bash
# 遍历目录
for i in /etc/rc.*; do
echo $i
done
# 遍历文件行
cat file.txt | while read line; do
echo $line
done
# C-like 循环
for ((i = 0 ; i < 100 ; i++)); do
echo $i
done
# 范围循环
for i in {1..5}; do
echo "Welcome $i"
done
# 范围循环,带step size(一次循环后增加step size,下面是5)
for i in {5..50..5}; do
echo "Welcome $i"
done
# while循环
while true; do
···
done
函数
bash
# 函数定义:直接写函数名
myfunc() {
echo "hello $1"
}
# 函数定义:使用了function关键字
function myfunc() {
echo "hello $1"
}
myfunc "John"
参数
$#
:参数个数$*
:所有参数(当成一个整体的字符串)$@
:所有参数(当成多个独立分开的字符串)$1
:第一个参数$2
:第二个参数$_
:上一个命令的最后一个参数
bash
function foo() {
echo "Number of arguments: $#" #=> 6
echo "All positional arguments (as a single word): $*" #=> 1 2 3 4 5 6
echo "All positional arguments (as separate strings): $@" #=> 1 2 3 4 5 6
echo "First argument: $1" #=> 1
echo "Second argument: $2" #=> 2
echo "------------------------------"
echo "last command args"
echo "Last argument of the previous command: $_" #=> last command args
echo "------------------------------"
for VAR in "$*"; do
echo "A single word $VAR" #=> "1 2 3 4 5 6"
done
echo "------------------------------"
for VAR in "$@"; do
echo "separate strings $VAR" #=> "1" "2" "3" "4" "5" "6"
done
}
foo 1 2 3 4 5 6
条件执行
bash
git commit && git push # 前面的命令执行成功,然后执行后面的命令
git commit || echo "Commit failed" # 前面的命令执行成功,不执行后面的命令
大小写转换
bash
STR="HELLO WORLD!"
echo ${STR,} #=> "hELLO WORLD!" (lowercase 1st letter)
echo ${STR,,} #=> "hello world!" (全部转小写)
STR="hello world!"
echo ${STR^} #=> "Hello world!" (uppercase 1st letter)
echo ${STR^^} #=> "HELLO WORLD!" (全部转大写)
数字比较
[[ NUM -eq NUM ]]
:是否相等(Equal)[[ NUM -ne NUM ]]
:是否不等(Not equal)[[ NUM -lt NUM ]]
:是否小于(Less than)[[ NUM -le NUM ]]
:是否小于并等于(Less than or equal)[[ NUM -gt NUM ]]
:是否大于(Greater than)[[ NUM -ge NUM ]]
:是否大于并等于(Greater than or equal)[[ STRING =~ STRING ]]
: (Regexp)(( NUM < NUM ))
: (Numeric conditions)
字符串比较
[[ -z STRING ]]
:字符串长度是否为0(表示是空字符串)(Zero)[[ -n STRING ]]
:字符串长度是否不为空(Not)[[ STRING == STRING ]]
:字符串是否相同(Equal)[[ STRING != STRING ]]
:字符串是否不相同(Not Equal)
文件比较
[[ -e FILE ]]
:检查FILE是否存在(e:Exists)[[ -r FILE ]]
:检查FILE是否可读(Readable)[[ -h FILE ]]
:检查FILE是否存在 Symlink[[ -d FILE ]]
:检查FILE是否存在并是一个目录(Directory)[[ -w FILE ]]
:检查FILE是否可写(Writable)[[ -s FILE ]]
:检查FILE是否存在并非空(Size)[[ -f FILE ]]
:检查FILE是否存在并是一个文件(File)[[ -x FILE ]]
:检查FILE是否存在并可执行(Executable)[[ FILE1 -nt FILE2 ]]
:检查FILE1是不是比FILE2更新(nt:Newer then)[[ FILE1 -ot FILE2 ]]
检查FILE1是不是比FILE2更老(ot:Older then)[[ FILE1 -ef FILE2 ]]
检查FILE1和FILE2是否相同(ef:Equal file)
其它比较
[[ -o noclobber ]]
:这个noclobber选项是否开启[[ ! EXPR ]]
:Not[[ X && Y ]]
:And[[ X || Y ]]
:Or
数组
bash
# declarations
Fruits=('Apple' 'Banana' 'Orange')
# assignment
Fruits[0]="Apple"
Fruits[1]="Banana"
Fruits[2]="Orange"
# Working with arrays
echo ${Fruits[0]} # 获取第0个元素
echo ${Fruits[-1]} # 获取最后一个元素
echo ${Fruits[@]} # 获取所有元素,每个是独立分开的
echo ${#Fruits[@]} # 获取元素个数,数组长度
echo ${#Fruits} # 获取每一个元素字符串的长度
echo ${#Fruits[3]} # 获取第三个元素字符串的长度
echo ${Fruits[@]:3:2} # 获取数组的一段Range范围 (from position 3, length 2)
echo ${!Fruits[@]} # 获取所有元素的Index索引:0、1、2...
# Operations
Fruits=("${Fruits[@]}" "Watermelon") # 添加元素
Fruits+=('Watermelon') # 也是添加元素
Fruits=( ${Fruits[@]/Ap*/} ) # 通过正则去删除
unset Fruits[2] # 删除元素
Fruits=("${Fruits[@]}") # 复制到一个新的数组
Fruits=("${Fruits[@]}" "${Veggies[@]}") # 追加数组
lines=(`cat "logfile"`) # 从文件中读,一行是一个元素的数组
# Iteration
for i in "${arrayName[@]}"; do
echo $i
done
选项
https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html
set -o noclobber
:避免重定向覆盖文件set -o errexit
:用于出错时退出,避免级联错误set -o pipefail
:在管道连接的命令中,只要有任何一个命令失败(返回值非0),则整个管道操作被视为失败set -o nounset
:使用了未定义的变量,立马退出
特殊变量
$?
:上一个命令的退出状态$!
:上一个后台任务的PID$$
:执行当前shell的PID$0
:执行当前shell的文件名 Filename of the shell script$_
:上一个命令的最后一个参数
其它常用命令
- command
- pushd、popd
- basename
- ...