Shell 基础语法

Shell 语言的基础语法,包括基础知识(变量、打印、运算符)、流程控制(分支、循环)和模块化(函数封装、文件封装、参数传递)三个部分。

本篇文章是 Shell脚本基础 的增强版。

一、基础知识

1.1 变量

变量定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 使用变量,建议方式
${var}
# 在变量名var后是空格时才可用如下方式
$var

# 字符串的定义
var_string="hello world"

# 只读变量
declare -r var_name
readonly var_name

# 删除变量
uset var_name

变量赋值

1
2
3
4
5
6
7
8
9
10
# 除了显式赋值,也可以将命令的结果存入到变量,如下就是将`ls /etc`的结果存到到了file_array
file_array=$( ls /etc )
# 也可以使用` 命令 `方式
file_array=` ls /etc `

# 应用:循环访问文件
for file in $( ls /etc )
do
# do something
done

数组变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 数组的定义
array_var=(val0 val1 val2)

# 单独定义每个元素
array_var[0]=val0
array_var[1]=val1

# 获取所有元素
${array_var[@]}

# 获取数组的长度/元素个数
${#array_var[@]}
${#array_var[*]}

# 获取单个元素长度
${#array_var[n]}

1.2 打印

echo

Shell 的 echo 指令与 PHP 的 echo 指令类似,都是用于字符串的输出。命令格式:

1
echo string
  • 显示普通字符串:
    echo "It is a test"

    这里的双引号完全可以省略(echo It is a test)

  • 显示转义字符
    echo "\"It is a test\""

    结果将是:”It is a test”,同样双引号也可以省略

  • 显示变量
    read 命令从标准输入中读取一行,并把输入行的每个字段的值指定给 shell 变量

    1
    2
    3
    4
    5
    6
    7
    #!/bin/sh
    read name
    echo "$name It is a test"
    # 以上代码保存为 test.sh, name 接收标准输入的变量,结果将是:
    [root@www ~]# sh test.sh
    OK #标准输入
    OK It is a test #输出
  • 显示换行
    echo -e "OK! \n" # -e 开启转义echo "It is a test"

    输出结果:

    OK! It is a test

  • 显示不换行
    #!/bin/sh echo -e "OK! \c" # -e 开启转义 \c 不换行 echo "It is a test"

    输出结果:

    OK! It is a test

  • 显示结果定向至文件
    echo "It is a test" > myfile

  • 原样输出字符串,不进行转义或取变量(用单引号)
    echo '$name\"'

    输出结果:$name"

  • 显示命令执行结果(这里因为无法打印出原字符所以加了转义符,实际不需要转义)
    echo date``,注意: 这里使用的是反引号`,而不是单引号’。结束时显示日期:Thu Jul 24 10:08:46 CST 2014

printf

printf 命令模仿 C 程序库(library)里的 printf() 程序。printf 由 POSIX 标准所定义,因此使用 printf 的脚本比使用 echo 移植性好。

printf 使用引用文本或空格分隔的参数,外面可以在 printf 中使用格式化字符串,还可以制定字符串的宽度、左右对齐方式等。默认的 printf 不会像 echo 自动添加换行符,我们可以手动添加 \n。

语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
printf  format-string  [arguments...]
# 参数说明
# format-string: 为格式控制字符串
# arguments: 为参数列表。
$ printf "Hello, Shell\n"
Hello, Shell

printf "%-10s %-8s %-4s\n" 姓名 性别 体重kg
printf "%-10s %-8s %-4.2f\n" 郭靖 男 66.1234
printf "%-10s %-8s %-4.2f\n" 杨过 男 48.6543
printf "%-10s %-8s %-4.2f\n" 郭芙 女 47.9876
>>>
姓名 性别 体重kg
郭靖 男 66.12
杨过 男 48.65
郭芙 女 47.99

# format-string为双引号
printf "%d %s\n" 1 "abc"
# 单引号与双引号效果一样
printf '%d %s\n' 1 "abc"
# 没有引号也可以输出
printf %s abcdef

# 格式只指定了一个参数,但多出的参数仍然会按照该格式输出,format-string 被重用
printf %s abc def
# 如果没有 arguments,那么 %s 用NULL代替,%d 用 0 代替
printf "%s and %d \n"

printf 转义序列:

序列 说明
\a 警告字符,通常为 ASCII 的 BEL 字符
\b 后退
\c 抑制(不显示)输出结果中任何结尾的换行字符(只在 %b 格式指示符控制下的参数字符串中有效),而且,任何留在参数里的字符、任何接下来的参数以及任何留在格式字符串中的字符,都被忽略
\f 换页(formfeed)
\n 换行
\r 回车(Carriage return)
\t 水平制表符
\v 垂直制表符
\ 一个字面上的反斜杠字符
\ddd 表示 1 到 3 位数八进制值的字符。仅在格式字符串中有效
\0ddd 表示 1 到 3 位的八进制值字符

1.3 运算符

算数运算符

运算符 说明 举例
+ 加法 expr $a + $b 结果为 30。
- 减法 expr $a - $b 结果为 -10。
* 乘法 expr $a * $b 结果为 200。
/ 除法 expr $b / $a 结果为 2。
% 取余 expr $b % $a 结果为 0。
= 赋值 a=$b 把变量 b 的值赋给 a。
== 相等。用于比较两个数字,相同则返回 true。 [ b ] 返回 false。
!= 不相等。用于比较两个数字,不相同则返回 true。 [ b ] 返回 true。

关系运算符

关系运算符只用于比较数字之间的关系,不支持字符串,除非字符串的值是数字

假定 a =10, b = 20

运算符 说明 举例
-eq 检测两个数是否相等,相等返回 true。 [ $a -eq $b ] 返回 false。
-ne 检测两个数是否不相等,不相等返回 true。 [ $a -ne $b ] 返回 true。
-gt 检测左边的数是否大于右边的,如果是,则返回 true。 [ $a -gt $b ] 返回 false。
-lt 检测左边的数是否小于右边的,如果是,则返回 true。 [ $a -lt $b ] 返回 true。
-ge 检测左边的数是否大于等于右边的,如果是,则返回 true。 [ $a -ge $b ] 返回 false。
-le 检测左边的数是否小于等于右边的,如果是,则返回 true。 [ $a -le $b ] 返回 true。

布尔运算符

假定 a =10, b = 20

运算符 说明 举例
! 非运算,表达式为 true 则返回 false,否则返回 true。 [ ! false ] 返回 true。
-o 或运算,有一个表达式为 true 则返回 true。 [ $a -lt 20 -o $b -gt 100 ] 返回 true。
-a 与运算,两个表达式都为 true 才返回 true。 [ $a -lt 20 -a $b -gt 100 ] 返回 false。

逻辑运算符

假定 a =10, b = 20

运算符 说明 举例
&& 逻辑的 AND [[ $a -lt 100 && $b -gt 100 ]] 返回 false

注意需要两个嵌套的中括号

字符串运算符

假定 var1=”abc”,var2=”efg”

运算符 说明 举例
= 检测字符串相等返回 true [ $var1 = $var2 ] 返回 false
!= 检测字符串不相等返回 true [ $var1 != $var2 ] 返回 true
-z 检测字符串长度为 0 返回 true [ -z $var1 ] 返回 false
-n 检测字符串长度不为 0 返回 true [ -n $var1 ] 返回 true
${} 检测字符串不为空返回 true [ $var1 ] 返回 true

文件测试运算符

使用方法:- file_name

操作符 说明
-e 检测文件是否存在
-d 检测目录是否存在
-f 普通文件检测(既不是目录,也不是设备文件)
-r 文件可读检测
-w 文件可写检测
-x 文件可执行检测
-b 块设备检测
-c 字符设备检测
-p 有名管道检测
-s 文件大小是否为 0
-S 文件是否是 socket 连接
-L 文件是否存在并且是一个符号链接
-g 文件 SGID 位检测
-u 文件 SUID 位检测
-k 文件粘滞位(Sticky Bit)检测

二、流程控制

2.1 分支

if-else

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# if
if condition; then
command
fi
# 写一行
if [ $(ps -ef | grep -c "ssh") -gt 1 ]; then echo "true"; fi

# if else
if condition; then
command1
else
command2
fi

# if else-if else
if condition1; then
command1
elif condition2; then
command2
else
commandN
fi

分支注意事项:

  • condition 的表示
    • if else 的 […] 判断语句中大于使用 -gt,小于使用 -lt(数值)
    • 如果使用 ((…)) 作为判断语句,大于和小于可以直接使用 > 和 <。

case … esac

case … esac 为多选择语句,与其他语言中的 switch … case 语句类似,是一种多分支选择结构,每个 case 分支用右圆括号开始,用 两个分号 ;; 表示 break,即执行结束,跳出整个 case … esac 语句,esac(就是 case 反过来)作为结束标记。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
case 值 in
模式1)
command1
command2
...
commandN
;;
模式2)
command1
command2
...
commandN
;;
esac

如果无一匹配模式,使用星号 * 捕获该值,再执行后面的命令

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
echo '输入 1 到 4 之间的数字:'
echo '你输入的数字为:'
read aNum
case $aNum in
1) echo '你选择了 1'
;;
2) echo '你选择了 2'
;;
3) echo '你选择了 3'
;;
4) echo '你选择了 4'
;;
*) echo '你没有输入 1 到 4 之间的数字'
;;
esac

2.2 循环

循环包括 for 循环、while 循环、无限循环和 util 循环。

for 循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
for var in item1 item2 ... itemN
do
command1
command2
...
commandN
done
# 一行
for var in item1 item2 ... itemN; do command1; command2… done;

# 示例
for loop in 1 2 3 4 5
do
echo "The value is: $loop"
done
>>>
The value is: 1
The value is: 2
The value is: 3
The value is: 4
The value is: 5

while 循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
while condition
do
command
done

# 示例
#!/bin/bash
int=1
while(( $int<=5 ))
do
echo $int
let "int++"
done
>>>
1
2
3
4
5

无限循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1
while :
do
command
done

# 2
while true
do
command
done

# 3
for (( ; ; ))

until 循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
until condition
do
command
done

# 示例
#!/bin/bash
a=0
until [ ! $a -lt 5 ]
do
echo $a
a=`expr $a + 1`
done
>>>
0
1
2
3
4

三、模块化

3.1 函数封装

函数格式:

1
2
3
4
5
6
7
8
[ function ] funname [()]
{

action;

[return int;]

}

说明:

  • 可以带 function fun() 定义,也可以直接 fun() 定义,不带任何参数
  • 参数返回,可以显示加:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。 return 后跟数值 n(范围:0-255)

3.2 文件封装

可以将某一部分内容放到一个文件里,作为一个更大的模块的封装。注意在调用该文件执行的时候,是启动了一个子 shell 来对文件进行解释操作的。

3.3 内容传递

模块之间的交互主要可以通过管道,参数,文件传递等方式

管道

管道方式是在调用多个函数的时候,将前一个命令的输出作为下一个命令的输入。管道传递的内容需要后一个命令具有解析输入的功能,一般后一个命令是 Linux 自带的命令。

参数

参数是在函数调用时可能用到的,用于不同模块内容的交互(参入主要是传入模块内容)

参数访问:

在函数体内部,通过 $n 的形式来获取参数的值,例如, $1 表示第一个参数,$2 表示第二个参数…

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
# author:菜鸟教程
# url:www.runoob.com

funWithParam(){
echo "第一个参数为 $1 !"
echo "第二个参数为 $2 !"
echo "第十个参数为 $10 !"
echo "第十个参数为 ${10} !"
echo "第十一个参数为 ${11} !"
echo "参数总数有 $# 个!"
echo "作为一个字符串输出所有参数 $* !"
}
funWithParam 1 2 3 4 5 6 7 8 9 34 73

参数处理:

使用 for 循环,遍历所有的参数,设置相应的标志位

text
1
2
3
4
5
6
7
8
9
10
11
12
13
# 对所有的参数预处理,得到参数标志
arg1_flag=0
for arg in ${*}
do
if [ ${arg} = "arg1" ]; then
arg1_flag=1
fi
done

# 根据参数标志判断是否执行某项操作
if [ ${arg1_flag} == 1 ]; then
# do something
fi

参数表示:(对外)

在对外展示 shell 脚本可以处理的参数的时候,需要对可选项等作出表示,shell 命令使用不同的括号来表示不同类型的参数

  • []:内容可写可不写
  • {}:必须要从{}中选择一个参数
  • <>:必选

特殊参数字符:

参数处理 说明
$# 传递到脚本或函数的参数个数
$* 以一个单字符串显示所有向脚本传递的参数
$$ 脚本运行的当前进程 ID 号
$! 后台运行的最后一个进程的 ID 号
$@ 与 $* 相同,但是使用时加引号,并在引号中返回每个参数。
$- 显示 Shell 使用的当前选项,与 set 命令功能相同。
$? 显示最后命令的退出状态。0 表示没有错误,其他任何值表明有错误。

文件传递

文件传递方式是将某些文本内容写入到文件,然后另一个命令或者模块利用该文件进行进一步的操作。

文件的用法:

  • 传递内容——写入到文件内容并由另一个模块或者命令读取
  • 设标志位——作为一个同步标志,控制运行流程

参考

【1】Shell 教程

【2】括号中的可选、必选表示