这是 Linux Bash shell 脚本入门的快速参考备忘单。
#!/bin/bash
VAR="world"
echo "Hello $VAR!" # => Hello world!
执行脚本
$ bash hello.sh
NAME="John"
echo ${NAME} # => John (变量)
echo $NAME # => John (变量)
echo "$NAME" # => John (变量)
echo '$NAME' # => $NAME (精确字符串)
echo "${NAME}!" # => John! (变量)
NAME = "John" # => 错误 (关于空格)
# 这是 Bash 的行内注释。
: '
这是一个
非常简洁的注释
在 bash 中
'
多行注释使用 :'
开始,'
结束
表达式 | 描述 |
---|---|
$1 … $9 |
参数 1 ... 9 |
$0 |
脚本本身的名称 |
$1 |
第一个参数 |
${10} |
位置参数 10 |
$# |
参数数量 |
$$ |
shell 的进程 ID |
$* |
所有参数 |
$@ |
所有参数,从第一个开始 |
$- |
当前选项 |
$_ |
上一个命令的最后一个参数 |
参见:特殊参数
代码 | 描述 |
---|---|
${FOO%suffix} |
移除后缀 |
${FOO#prefix} |
移除前缀 |
${FOO%%suffix} |
移除长后缀 |
${FOO##prefix} |
移除长前缀 |
${FOO/from/to} |
替换首次匹配 |
${FOO//from/to} |
全部替换 |
${FOO/%from/to} |
替换后缀 |
${FOO/#from/to} |
替换前缀 |
表达式 | 描述 |
---|---|
${FOO:0:3} |
子字符串 (位置, 长度) |
${FOO:(-3):3} |
从右侧开始的子字符串 |
表达式 | 描述 |
---|---|
${#FOO} |
$FOO 的长度 |
表达式 | 描述 |
---|---|
${FOO:-val} |
$FOO ,如果未设置则为 val |
${FOO:=val} |
如果未设置,则将 $FOO 设置为 val |
${FOO:+val} |
如果 $FOO 已设置,则为 val |
${FOO:?message} |
如果 $FOO 未设置,则显示消息并退出 |
echo ${food:-Cake} #=> $food 或 "Cake"
STR="/path/to/foo.cpp"
echo ${STR%.cpp} # /path/to/foo
echo ${STR%.cpp}.o # /path/to/foo.o
echo ${STR%/*} # /path/to
echo ${STR##*.} # cpp (扩展名)
echo ${STR##*/} # foo.cpp (基本路径)
echo ${STR#*/} # path/to/foo.cpp
echo ${STR##*/} # foo.cpp
echo ${STR/foo/bar} # /path/to/bar.cpp
name="John"
echo ${name} # => John
echo ${name:0:2} # => Jo
echo ${name::2} # => Jo
echo ${name::-1} # => Joh
echo ${name:(-1)} # => n
echo ${name:(-2)} # => hn
echo ${name:(-2):2} # => hn
length=2
echo ${name:0:length} # => Jo
参见:参数展开
SRC="/path/to/foo.cpp"
BASEPATH=${SRC##*/}
echo $BASEPATH # => "foo.cpp"
DIRPATH=${SRC%$BASEPATH}
echo $DIRPATH # => "/path/to/"
STR="HELLO WORLD!"
echo ${STR,} # => hELLO WORLD!
echo ${STR,,} # => hello world!
STR="hello world!"
echo ${STR^} # => Hello world!
echo ${STR^^} # => HELLO WORLD!
ARR=(hello World)
echo "${ARR[@],}" # => hello world
echo "${ARR[@]^}" # => Hello World
Fruits=('Apple' 'Banana' 'Orange')
Fruits[0]="Apple"
Fruits[1]="Banana"
Fruits[2]="Orange"
ARRAY1=(foo{1..2}) # => foo1 foo2
ARRAY2=({A..D}) # => A B C D
# 合并 => foo1 foo2 A B C D
ARRAY3=(${ARRAY1[@]} ${ARRAY2[@]})
# declare 结构
declare -a Numbers=(1 2 3)
Numbers+=(4 5) # 追加 => 1 2 3 4 5
- | - |
---|---|
${Fruits[0]} |
第一个元素 |
${Fruits[-1]} |
最后一个元素 |
${Fruits[*]} |
所有元素 |
${Fruits[@]} |
所有元素 |
${#Fruits[@]} |
元素总数 |
${#Fruits} |
第一个元素的长度 |
${#Fruits[3]} |
第 n 个元素的长度 |
${Fruits[@]:3:2} |
范围 |
${!Fruits[@]} |
所有键 |
Fruits=('Apple' 'Banana' 'Orange')
for e in "${Fruits[@]}"; do
echo $e
done
for i in "${!Fruits[@]}"; do
printf "%s\t%s\n" "$i" "${Fruits[$i]}"
done
Fruits=("${Fruits[@]}" "Watermelon") # 推入 (Push)
Fruits+=('Watermelon') # 也可推入 (Push)
Fruits=( ${Fruits[@]/Ap*/} ) # 通过正则表达式匹配移除
unset Fruits[2] # 移除一个项目
Fruits=("${Fruits[@]}") # 复制
Fruits=("${Fruits[@]}" "${Veggies[@]}") # 连接
lines=(`cat "logfile"`) # 从文件读取
function extract()
{
local -n myarray=$1
local idx=$2
echo "${myarray[$idx]}"
}
Fruits=('Apple' 'Banana' 'Orange')
extract Fruits 2 # => Orange
declare -A sounds
sounds[dog]="bark"
sounds[cow]="moo"
sounds[bird]="tweet"
sounds[wolf]="howl"
echo ${sounds[dog]} # 狗的声音
echo ${sounds[@]} # 所有值
echo ${!sounds[@]} # 所有键
echo ${#sounds[@]} # 元素数量
unset sounds[dog] # 删除狗
for val in "${sounds[@]}"; do
echo $val
done
for key in "${!sounds[@]}"; do
echo $key
done
条件 | 描述 |
---|---|
[[ NUM -eq NUM ]] |
|
[[ NUM -ne NUM ]] |
|
[[ NUM -lt NUM ]] |
|
[[ NUM -le NUM ]] |
|
[[ NUM -gt NUM ]] |
|
[[ NUM -ge NUM ]] |
|
(( NUM < NUM )) |
小于 |
(( NUM <= NUM )) |
小于或等于 |
(( NUM > NUM )) |
大于 |
(( NUM >= NUM )) |
大于或等于 |
条件 | 描述 |
---|---|
[[ -z STR ]] |
空字符串 |
[[ -n STR ]] |
|
[[ STR == STR ]] |
相等 |
[[ STR = STR ]] |
相等 (同上) |
[[ STR < STR ]] |
小于 (ASCII) |
[[ STR > STR ]] |
大于 (ASCII) |
[[ STR != STR ]] |
不相等 |
[[ STR =~ STR ]] |
正则表达式 |
if [[ -z "$string" ]]; then
echo "字符串为空"
elif [[ -n "$string" ]]; then
echo "字符串不为空"
else
echo "这永远不会发生"
fi
if [[ X && Y ]]; then
...
fi
if [[ "$A" == "$B" ]]; then
...
fi
if [[ '1. abc' =~ ([a-z]+) ]]; then
echo ${BASH_REMATCH[1]}
fi
if (( $a < $b )); then
echo "$a 小于 $b"
fi
if [[ -e "file.txt" ]]; then
echo "文件存在"
fi
条件 | 描述 |
---|---|
[[ -e FILE ]] |
|
[[ -d FILE ]] |
|
[[ -f FILE ]] |
|
[[ -h FILE ]] |
符号链接 |
[[ -s FILE ]] |
大小 > 0 字节 |
[[ -r FILE ]] |
|
[[ -w FILE ]] |
|
[[ -x FILE ]] |
可执行 |
[[ f1 -nt f2 ]] |
f1 |
[[ f1 -ot f2 ]] |
f2 |
[[ f1 -ef f2 ]] |
相同文件 |
条件 | 描述 |
---|---|
[[ -o noclobber ]] |
如果选项已启用 |
[[ ! EXPR ]] |
非 |
[[ X && Y ]] |
与 |
[[ X | | Y ]] |
或 |
if [ "$1" = 'y' -a $2 -gt 0 ]; then
echo "yes"
fi
if [ "$1" = 'n' -o $2 -lt 0 ]; then
echo "no"
fi
for i in /etc/rc.*; do
echo $i
done
for ((i = 0 ; i < 100 ; i++)); do
echo $i
done
for number in $(seq 1 3); do
if [[ $number == 2 ]]; then
continue;
fi
echo "$number"
done
for number in $(seq 1 3); do
if [[ $number == 2 ]]; then
# 跳过循环的其余部分。
break;
fi
# 这只会打印 1
echo "$number"
done
count=0
until [ $count -gt 10 ]; do
echo "$count"
((count++))
done
i=1
while [[ $i -lt 4 ]]; do
echo "Number: $i"
((i++))
done
i=3
while [[ $i -gt 0 ]]; do
echo "Number: $i"
((i--))
done
i=3
while ((i--)); do
echo "Number: $i"
done
while true; do
# 这里是一些代码。
done
while :; do
# 这里是一些代码。
done
cat file.txt | while read line; do
echo $line
done
# 避免覆盖文件
# (echo "hi" > foo)
set -o noclobber
# 用于在出错时退出
# 避免级联错误
set -o errexit
# 揭示隐藏的失败
set -o pipefail
# 暴露未设置的变量
set -o nounset
# 不匹配的 glob 被移除
# ('*.foo' => '')
shopt -s nullglob
# 不匹配的 glob 抛出错误
shopt -s failglob
# 不区分大小写的 glob
shopt -s nocaseglob
# 通配符匹配点文件
# ("*.sh" => ".foo.sh")
shopt -s dotglob
# 允许 ** 进行递归匹配
# ('lib/**/*.rb' => 'lib/a/b/c.rb')
shopt -s globstar
命令 | 描述 |
---|---|
history |
显示历史 |
sudo !! |
使用 sudo 运行上一个命令 |
shopt -s histverify |
不要立即执行展开的结果 |
表达式 | 描述 |
---|---|
!$ |
展开最近命令的最后一个参数 |
!* |
展开最近命令的所有参数 |
!-n |
展开第 n 个最近的命令 |
!n |
展开历史中的第 n 个命令 |
!<command> |
展开命令 <command> 的最近一次调用 |
代码 | 描述 |
---|---|
!! |
再次执行上一个命令 |
!!:s/<FROM>/<TO>/ |
在最近的命令中将 <FROM> 的首次出现替换为 <TO> |
!!:gs/<FROM>/<TO>/ |
在最近的命令中将 <FROM> 的所有出现替换为 <TO> |
!$:t |
仅从最近命令的最后一个参数展开基本名称 |
!$:h |
仅从最近命令的最后一个参数展开目录 |
!!
和 !$
可以替换为任何有效的展开。
代码 | 描述 |
---|---|
!!:n |
仅从最近的命令展开第 n 个标记 (命令是 0 ;第一个参数是 1 ) |
!^ |
从最近的命令展开第一个参数 |
!$ |
从最近的命令展开最后一个标记 |
!!:n-m |
从最近的命令展开标记范围 |
!!:n-$ |
从最近的命令展开第 n 个标记到最后一个标记 |
!!
可以替换为任何有效的展开,例如 !cat
、!-2
、!42
等。
$((a + 200)) # 将 $a 增加 200
$(($RANDOM%200)) # 随机数 0..199
(cd somedir; echo "我现在在 $PWD")
pwd # 仍然在第一个目录
command -V cd
#=> "cd 是一个函数/别名/等等"
python hello.py > output.txt # stdout 到 (文件)
python hello.py >> output.txt # stdout 到 (文件), 追加
python hello.py 2> error.log # stderr 到 (文件)
python hello.py 2>&1 # stderr 到 stdout
python hello.py 2>/dev/null # stderr 到 (空设备)
python hello.py &>/dev/null # stdout 和 stderr 到 (空设备)
python hello.py < foo.txt # 将 foo.txt 提供给 python 的 stdin
source "${0%/*}/../share/foo.sh"
DIR="${0%/*}"
case "$1" in
start | up)
vagrant up
;;
*)
echo "用法: $0 {start|stop|ssh}"
;;
esac
trap 'echo 大约在 $LINENO 行发生错误' ERR
或
traperr() {
echo "错误: ${BASH_SOURCE[1]} 大约在 ${BASH_LINENO[0]} 行"
}
set -o errtrace
trap traperr ERR
printf "Hello %s, I'm %s" Sven Olga
#=> "Hello Sven, I'm Olga
printf "1 + 1 = %d" 2
#=> "1 + 1 = 2"
printf "Print a float: %f" 2
#=> "Print a float: 2.000000"
while [[ "$1" =~ ^- && ! "$1" == "--" ]]; do case $1 in
-V | --version )
echo $version
exit
;;
-s | --string )
shift; string=$1
;;
-f | --flag )
flag=1
;;
esac; shift; done
if [[ "$1" == '--' ]]; then shift; fi
if ping -c 1 google.com; then
echo "看起来你有可用的网络连接"
fi
if grep -q 'foo' ~/.bash_history; then
echo "你过去似乎输入过 'foo'"
fi
使用 \
转义这些特殊字符
cat <<END
hello world
END
pwd # /home/user/foo
cd bar/
pwd # /home/user/foo/bar
cd -
pwd # /home/user/foo
echo -n "继续吗? [y/n]: "
read ans
echo $ans
read -n 1 ans # 仅一个字符
git commit && git push
git commit || echo "提交失败"
set -euo pipefail
IFS=$'\n\t'
args=("$@")
args+=(foo)
args+=(bar)
echo "${args[@]}"
将参数放入数组然后追加