Post

Shell教程

运行Shell脚本

Shell脚本可以直接执行Shell命令,例如:

1
2
3
echo Hello, world!
date
ls -l $HOME

为了运行Shell脚本,将上面的代码保存到文件test.sh,并在执行以下命令

1
bash test.sh

另一种方式是在脚本开头添加一行#!/bin/bash,之后执行以下命令

1
2
chmod +x test.sh
./test.sh

第一行命令给脚本添加可执行权限(只需执行一次),第二行中的./表示在当前目录中查找可执行文件。

退出脚本

1
exit [code]

状态码默认为0。

输入与输出

1
2
3
echo "What's your name?"
read name
echo "Hello, $name!"
1
2
3
4
$ bash test.sh
What's your name?
Alice
Hello, Alice!

变量和字符串

变量赋值:

1
str="Hello, world!"

注意等号前后不能有空格。

引用变量:$var${var}

1
2
3
4
echo "str="$str
echo "a\"bc[$str]d\$ef\\g"
echo 'a\"bc[$str]d\$ef\\g'
echo "undefined="$undefined

输出如下:

1
2
3
4
str=Hello, world!
a"bc[Hello, world!]d$ef\g
a\"bc[$str]d\$ef\\g
undefined=

获取命令输出

可以使用反引号或$()获取其他命令的标准输出:

1
2
3
4
5
6
path=`pwd`
echo $path
path1="`pwd`\include"
path2='`pwd`\include'
echo "path1=$path1"
echo "path2=$path2"

输出如下:

1
2
3
/home/zzy
path1=/home/zzy/include
path2=`pwd`/include

语句

if语句

语法:

1
2
3
4
5
6
7
if cond; then
  cmd_list;
[elif cond; then
  cmd_list;]
[else
  cmd_list;]
fi

cond为测试命令,返回值为0表示真,非0表示假。

测试命令:test[,条件为真则返回0,否则返回1。

逻辑运算符

表达式含义
!expr
( expr )括号
expr1 -a expr2
expr1 -o expr2

整数比较

表达式含义
a -eq ba等于b
a -ne ba不等于b
a -lt ba小于b
a -le ba小于等于b
a -gt ba大于b
a -ge ba大于等于b

字符串比较

表达式含义
str1 = str2str1 == str2字符串相等
str1 != str2字符串不相等
str1 < str2str1按字典序小于str2
str1 > str2str1按字典序大于str2
-z str字符串为空(未定义不是空)
-n strstr字符串非空

注意:<>需用\转义,否则被认为是重定向符号。

文件判断

表达式含义
-f file判断文件是否存在
-d file判断目录是否存在
-r file判断文件是可读
-w file判断文件是可写
-x file判断文件是可执行

例如:

1
2
3
4
5
6
7
8
9
10
11
if [ -f ./a.txt ]; then
  echo "`pwd`/a.txt存在"
  n=`cat ./a.txt | wc -l`
  if [ $n -gt 10 ]; then
    echo "内容大于10行"
  else
    echo "内容小于等于10行"
  fi
else
  echo "`pwd`/a.txt不存在"
fi

组合命令

表达式含义
cmd1 && cmd2当且仅当cmd1返回值为0时才执行cmd2
cmd1 || cmd2当且仅当cmd1返回值为非0时才执行cmd2

组合命令的返回值是最后执行的命令的返回值。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
echo "请输入成绩:"
read grade
if [ $grade -gt 100 -o $grade -lt 0 ]; then
  echo "请输入0~100之间的数"
else
  if [ $grade -ge 90 ]; then
    echo "Excellent"
  elif [ $grade -ge 80 -a $grade -le 89 ]; then
    echo "Good"
  elif [ $grade -ge 70 ] && [ $grade -le 79 ]; then
    echo "Middle"
  elif [ $grade -ge 60 ] && [ $grade -le 69 ]; then
    echo "Passing"
  else
    echo "Bad"
  fi
fi

简写形式

表达式含义
[ cond ] && cmdif-then简写形式
[ cond ] && cmd1 || cmd2if-then-else简写形式

例如:

1
[ `whoami` = "alice" ] && echo "你是alice" || echo "你不是alice"

case语句

语法:

1
2
3
4
5
6
case expr in
  pattern|pattern...)
    cmd_list
    ;;
  ...
esac

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
echo -n "请输入动物名称:"
read animal
echo -n "The $animal has "
case $animal in
  "horse" | "dog" | "cat")
    echo -n "four"
    ;;
  "man" | "kangaroo")
    echo -n "two"
    ;;
  *)
    echo -n "an unknown number of"
    ;;
esac
echo " legs."

while语句

语法:

1
2
3
while cond; do
  cmd_list;
done

当测试命令cond的返回值为0时循环执行语句。例如:

1
2
3
4
5
6
7
8
i=0
while [ $i -lt 10 ]; do
  echo -n "$i "
  ((i++))
  # i=$(($i + 1))
  # i=`expr $i + 1`
done
echo

输出如下:

1
0 1 2 3 4 5 6 7 8 9 

until语句

语法:

1
2
3
until cond; do
  cmd_list;
done

当测试命令cond的返回值为非0时循环执行语句。例如:

1
2
3
4
5
6
i=9
until [ $i -lt 0 ]; do
  echo -n "$i "
  i=$(($i - 1))
done
echo

输出如下:

1
9 8 7 6 5 4 3 2 1 0 

for语句

简单for循环,类C语言

1
2
3
4
for ((i=1;i<=10;++i)); do
  echo -n "$i "
done
echo
1
1 2 3 4 5 6 7 8 9 10 

遍历列表

1
2
3
4
for i in 1 2 3 4 5; do
  echo -n "$(expr $i \* $i) "
done
echo
1
1 4 9 16 25 

使用seq命令生成列表

1
2
3
4
for i in `seq 0 2 10`; do
  echo -n "$i "
done
echo
1
0 2 4 6 8 10 

使用反引号或$()将命令的输出作为列表

1
2
3
for file in `ls $HOME`; do
  echo "$file"
done

将列表赋给变量

1
2
3
4
5
6
list="alpha beta gamma"
list=$list" delta"
for i in $list; do
  echo -n "$i "
done
echo
1
alpha beta gamma delta 

遍历命令行参数

  • 各命令行参数:$0(脚本名)、$1$2
  • 参数个数:$#(不包括$0
  • 参数列表:$*$@(可用于for循环,不包括$0

例如:

1
2
3
4
5
echo "命令行参数个数:$#"
echo "全部命令行参数:"
for arg in $*; do
  echo "$arg"
done
1
2
3
4
5
6
$ bash test.sh foo bar baz
命令行参数个数:3
全部命令行参数:
foo
bar
baz

读取文件

方法一:cat+while+read

1
2
3
4
5
6
echo "Hello, world!" > test.txt
echo -e "A new Line.\nAnd another line." >> test.txt
cat test.txt | while read line; do
  echo $line
done
rm test.txt

输出如下:

1
2
3
Hello, world!
A new Line.
And another line.

方法二:for+cat

for-in语句默认的分隔符为空白符,由IFS环境变量指定

1
2
3
4
5
6
7
8
echo -e "Hello, world!\nA new Line.\nAnd another line." > test.txt
OLD_IFS=$IFS
IFS=$'\n'
for line in $(cat test.txt); do
  echo $line
done
IFS=$OLD_IFS
rm test.txt

数组

定义数组:用括号表示,元素用空格分隔

1
2
num_arr=(0 1 2 3 4 5 6 7)
str_arr=("foo" "bar" "baz")

数组长度:${#arr[@]}${#arr[*]}

1
2
echo "num_arr.length = "${#num_arr[@]}
echo "str_arr.length = "${#str_arr[*]}
1
2
num_arr.length = 8
str_arr.length = 3

遍历数组:${arr[@]}${arr[*]}

1
2
3
4
echo "num_arr = [ ${num_arr[@]} ]"
for x in ${num_arr[@]}; do
  echo $x
done
1
2
3
4
5
6
7
8
9
num_arr = [ 0 1 2 3 4 5 6 7 ]
0
1
2
3
4
5
6
7

分片访问:${arr[*/@]:start:length}

1
2
echo "num_arr[2:4] = [ "${num_arr[@]:2:4}" ]"
echo "str_arr = [ "${str_arr[@]:0:${#str_arr[*]}}" ]"
1
2
num_arr[2:4] = [ 2 3 4 5 ]
str_arr = [ foo bar baz ]

通过下标访问:${arr[index]}

1
2
3
4
5
6
echo "num_arr[6] = "${num_arr[6]}
i=3
echo "num_arr[$i] = "${num_arr[$i]}
str_arr[1]="qux"
str_arr[10]="quux"  # 如果下标越界则添加到尾部
echo "After setting: str_arr = [ "${str_arr[@]}" ]"
1
2
3
num_arr[6] = 6
num_arr[3] = 3
After setting: str_arr = [ foo qux baz quux ]

删除元素:unset arr[index],删除整个数组:unset arr

1
2
unset num_arr[3]
echo "After deleting num_arr[3]: num_arr = [ "${num_arr[@]}" ]"
1
After deleting num_arr[3]: num_arr = [ 0 1 2 4 5 6 7 ]

模式替换:${arr[*/@]/pattern/replacement}

参考

GNU Bash参考手册

This post is licensed under CC BY 4.0 by the author.