bash备忘

[TOC]

符号

  • $# 传入脚本的参数个数(不包括argv[0])
  • $? 读取最后执行命令的退出码
  • $$ 运行脚本进程的PID
  • $@ 所有的位置参数,每个参数由引号引用。
  • $* 所有的位置参数,和$@区别在于参数不被引号引用。

数组

BASH只支持一维数组,但参数个数没有限制。

声明一个数组:

declare -a array

数组赋值,有多种方法:

array=(var1 var2 var3 ... varN)

array=([0]=var1 [1]=var2 [2]=var3 ... [n]=varN)

array[0]=var1
arrya[1]=var2
...
array[n]=varN

计算数组元素个数:${#array[@]} 或者 ${#array[*]},BASH的特殊参数 @* 都表示 “扩展位置参数,从1开始”,在数组里可以通用。

提取数组里第n个元素(从0开始): ${array[n]}

提取数组里从下标n开始的m个元素: ${array[@]:n:m}

提取数组里第n个元素的长度: ${#array[n]}

遍历数组:

filename=($(ls))
for var in ${filename[@]}; do
    echo $var
done

比如,从“标准输入”读入n次字符串,每次输入的字符串保存在数组array里:

i=0
n=5
while [ "$i" -lt $n ] ; do
  echo "Please input strings ... `expr $i + 1`"
  read array[$i]
  b=${array[$i]}
  echo "$b"
  i=`expr $i + 1`
done

字符串

可以用符号 ${} 来进行各种字符串操作。

比如,对于这样的字符串 filename=/dir1/dir2/file.txt

提取子串

可以用 ${filename:n:m} 来提取从下标n开始的m个字符。有多少提取多少,否则输出空串,不会报错。

截取子串

可以用 ${} 匹配字符来截取相应的子串。

示例 说明
${filename#*/} 删除最左边 / 及其左边的字符串:dir1/dir2/file.txt
${filename##*/} 删除最右边 / 及其左边的字符串: file.txt
${filename#/dir1/dir2} 删除最左边 /dir1/dir2 变成: /file.txt
${filename##*file} 删除从最右边 file 及其左边的字符串: .txt
${filename%/*} 删除最右边 / 及其右边的字符串: /dir1/dir2
${filename%%/*} 删除最左边 / 及其右边的字符串: (空字符串)
${filename%dir*} 删除最右边 dir 及其右边的字符串 /dir1/

字符串替换

示例 说明
${filename/dir/folder} 匹配第一个dir并替换为folder/folder1/dir2/file.txt
${filename//dir/folder} 匹配所有dir并替换为folder/folder1/folder2/file.txt
${filename/#\/dir1/\/folder1} 匹配/dir1开头的字符串并替换为/folder1/folder1/dir2/file.txt
${filename/%.txt/.doc} 匹配 .txt结尾的字符串并替换为.doc/dir1/dir2/file.doc

区别于工具tr,后者是集合替换。比如,把字符串里所有的大写字母转换成小写:

echo $filename | tr A-Z a-z

控制结构

if condition

if [ ! -d $DIR -o -f $FILE ]; then
    echo "not directory"
elif [ -z $1 ]; then
    echo "empty arguments"
else
    exit -1
fi
操作符 说明
-a 逻辑与
-d 判断目录
-eq 俩数值相等
-ne 俩数值不相等, eg: if [ "$UID" -ne "$ROOT_UID" ]
== 俩字符串相等
!= 俩字符串不等
-f 判断文件
-n 值非空
-o 逻辑或
-z 值为空

switch

case $HOST_TYPE in
    "mac")
        echo "mac"
        ;;
    "linux")
        echo "linux"
        ;;
    *)
        echo "unknown"
        ;;
esac

for loop

for Lang in $(find "$1" -name "*.lproj"); do
    echo $Lang
done

while loop

i="0"; time while [ $i -lt 10 ]; do dd if=/dev/zero of=fill bs=1048576 count=64 conv=fdatasync; i=$[$i+1]; done

while true; do dd if=/dev/zero of=fill1 bs=1048576 count=4000 conv=fdatasync; done

输入输出

读文件

比如,hostinfo.txt 里的内容有 host user passwd 三列,有多种读取方法。

可以直接解析到相应变量:

{
while read host user passwd;  do
    echo $host $user $passwd
done
} < $HOME/hostinfo.txt

或者,逐行读取再做处理:

cat $HOME/hostinfo.txt | while read line; do
    echo $line
done

或者,先把文件读到一个数组里,然后再做处理:

hostinfo=($(cat $HOME/hostinfo.txt))
for line in ${hostinfo[@]}; do
    echo $line
done

管道

利用有名管道,可以把一堆文本压缩成一个单一的gz文件,并且中间不需要创建临时文件。

$ mkfifo /tmp/foo
$ cat /tmp/foo | gzip > bar.gz
$ for i in $(find ...); do cat $i > /tmp/foo; done

重定向

示例 说明
cmd >&n 重定向stdout到文件描述符n
cmd m>&n 重定向文件描述符m到文件描述符n
cmd >&- 关闭stdout
cmd <&n 重定向文件描述符n到stdin
cmd m<&n 重定向文件描述符n到文件描述符m
cmd <&- 关闭stdin
cmd > file 2>&1 重定向stdout和stderr到文件file
cmd │ tee file 不影响cmd的IO,同时复制stdout到文件file