入门指南
hello.sh
#!/bin/bash
VAR="world"
echo "Hello $VAR!"
执行脚本
bash hello.sh
变量
NAME="John"
echo ${NAME}
echo $NAME
echo "$NAME"
echo '$NAME'
echo "${NAME}!"
NAME = "John"
注释
: '
这是一个
非常整洁的
Bash注释
'
多行注释使用 :'
开始,'
结束
参数
表达式 |
描述 |
$1 … $9 |
参数1 … 9 |
$0 |
脚本本身的名字 |
$1 |
第一个参数 |
${10} |
位置参数10 |
$# |
参数数量 |
$$ |
Shell的进程ID |
$* |
所有参数 |
$@ |
所有参数,从第一个开始 |
$- |
当前选项 |
$_ |
上一个命令的最后一个参数 |
参见: 特殊参数
函数
get_name() {
echo "John"
}
echo "You are $(get_name)"
参见: 函数
条件判断
if [[ -z "$string" ]]; then
echo "字符串为空"
elif [[ -n "$string" ]]; then
echo "字符串不为空"
fi
参见: 条件判断
花括号扩展
echo {
A,B}.js
表达式 |
描述 |
{A,B} |
等同于 A B |
{A,B}.js |
等同于 A.js B.js |
{1..5} |
等同于 1 2 3 4 5 |
参见: 花括号扩展
Shell执行
echo "我在 $(PWD)"
echo "我在 `pwd`"
参见: 命令替换
Bash参数扩展
语法
代码 |
描述 |
${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:-val} |
$FOO ,或如果未设置则为 val |
${FOO:=val} |
如果未设置则将 $FOO 设为 val |
${FOO:+val} |
如果 $FOO 已设置则为 val |
${FOO:?message} |
如果未设置 $FOO 则显示消息并退出 |
替换
echo ${food:-Cake}
STR="/path/to/foo.cpp"
echo ${STR%.cpp}
echo ${STR%.cpp}.o
echo ${STR%/*}
echo ${STR##*.}
echo ${STR##*/}
echo ${STR#*/}
echo ${STR##*/}
echo ${STR/foo/bar}
切片
name="John"
echo ${name}
echo ${name:0:2}
echo ${name::2}
echo ${name::-1}
echo ${name:(-1)}
echo ${name:(-2)}
echo ${name:(-2):2}
length=2
echo ${name:0:length}
参见: 参数扩展
基路径 & 目录路径
SRC="/path/to/foo.cpp"
BASEPATH=${SRC##*/}
echo $BASEPATH
DIRPATH=${SRC%$BASEPATH}
echo $DIRPATH
转换
STR="HELLO WORLD!"
echo ${STR,}
echo ${STR,,}
STR="hello world!"
echo ${STR^}
echo ${STR^^}
ARR=(hello World)
echo "${ARR[@],}"
echo "${ARR[@]^}"
Bash数组
定义数组
Fruits=('Apple' 'Banana' 'Orange')
Fruits[0]="Apple"
Fruits[1]="Banana"
Fruits[2]="Orange"
ARRAY1=(foo{
1..2})
ARRAY2=({
A..D})
ARRAY3=(${ARRAY1[@]} ${ARRAY2[@]})
declare -a Numbers=(1 2 3)
Numbers+=(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")
Fruits+=('Watermelon')
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
Bash字典
定义
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
Bash条件判断
整数条件
条件 |
描述 |
[[ 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 比 f2 新 |
[[ f1 -ot f2 ]] |
f2 比 f1 旧 |
[[ f1 -ef f2 ]] |
相同文件 |
更多条件
条件 |
描述 |
[[ -o noclobber ]] |
如果选项已启用 |
[[ ! EXPR ]] |
不是 |
[[ X && Y ]] |
并且 |
`[[ X |
|
逻辑和,或
if [ "$1" = 'y' -a $2 -gt 0 ]; then
echo "是"
fi
if [ "$1" = 'n' -o $2 -lt 0 ]; then
echo "否"
fi
Bash循环
基本的for循环
for i in /etc/rc.*; do
echo $i
done
类C的for循环
for ((i = 0 ; i < 100 ; i++)); do
echo $i
done
范围
for i in {
1..5}; do
echo "欢迎 $i"
done
带步长
for i in {
5..50..5}; do
echo "欢迎 $i"
done
自动增量
i=1
while [[ $i -lt 4 ]]; do
echo "数字: $i"
((i++))
done
自动减量
i=3
while [[ $i -gt 0 ]]; do
echo "数字: $i"
((i--))
done
continue
for number in $(seq 1 3); do
if [[ $number == 2 ]]; then
continue;
fi
echo "$number"
done
break
for number in $(seq 1 3); do
if [[ $number == 2 ]]; then
break;
fi
echo "$number"
done
until
count=0
until [ $count -gt 10 ]; do
echo "$count"
((count++))
done
死循环
while true; do
done
死循环 (简写)
while :; do
done
读取行
cat file.txt | while read line; do
echo $line
done
Bash函数
定义函数
myfunc() {
echo "你好 $1"
}
function myfunc() {
echo "你好 $1"
}
myfunc "John"
返回值
myfunc() {
local myresult='某个值'
echo $myresult
}
result="$(myfunc)"
抛出错误
myfunc() {
return 1
}
if myfunc; then
echo "成功"
else
echo "失败"
fi
Bash选项
选项
set -o noclobber
set -o errexit
set -o pipefail
set -o nounset
通配选项
shopt -s nullglob
shopt -s failglob
shopt -s nocaseglob
shopt -s dotglob
shopt -s globstar
Bash历史
命令
命令 |
描述 |
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 个参数 |
!^ |
扩展最近命令的第一个参数 |
!$ |
扩展最近命令的最后一个参数 |
!!:n-m |
扩展最近命令的第 n 个到第 m 个参数 |
!!:n-$ |
扩展最近命令的第 n 个参数到最后一个参数 |
!!
可以替换为任何有效的扩展,例如 !cat
,!-2
,!42
等。
其他
数值计算
$((a + 200))
$(($RANDOM%200))
子Shell
(cd somedir; echo "我现在在 $PWD")
pwd
检查命令
command -V cd
重定向
python hello.py > output.txt
python hello.py >> output.txt
python hello.py 2> error.log
python hello.py 2>&1
python hello.py 2>/dev/null
python hello.py &>/dev/null
python hello.py < foo.txt
相对来源
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
printf "Hello %s, I'm %s" Sven Olga
printf "1 + 1 = %d" 2
printf "打印一个浮点数: %f" 2
获取选项
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
特殊变量
表达式 |
描述 |
$? |
上一个任务的退出状态 |
$! |
上一个后台任务的PID |
$$ |
Shell的PID |
$0 |
Shell脚本的文件名 |
参见 特殊参数.
Grep检查
if grep -q 'foo' ~/.bash_history; then
echo "你似乎在过去曾经输入过 'foo'"
fi
反斜杠转义
 
;
\!
\"
\#
\&
\'
\(
\)
\,
\;
\<
\>
\[
\|
\\
\]
\^
\{
\}
- ```
\$
\*
\?
使用 \
转义这些特殊字符
Heredoc
cat <<END
hello world
END
返回上一个目录
pwd
cd bar/
pwd
cd -
pwd
读取输入
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'
参见: 非官方的bash严格模式
可选参数
args=("$@")
args+=(foo)
args+=(bar)
echo "${args[@]}"
将参数放入数组然后追加
另见