Linux Shell精简教程

Linux Shell教程

Linux的Shell脚本是Linux的基础之一,学习Shell语法是Linux的必经之路。下面来讲Shell的语法。

脚本解释器,即shell的种类,有:bash,sh,ash,csh(tcsh),ksh.
注意,bash完全兼容sh,一般写sh类型的或bash类型的(後缀sh)。

开头"#!"後接shell执行环境,如:"#!/bin/sh","#!/bin/bash"

关于sh和bash的区别,简单讲即sh是bash --posix,详细的请自查:

POSIX shell的教程
Bash参考手册

一、shell变量

  1. 定义变量,使用赋值语句,如:variableName=value(等号两边没有空格)。
  2. 引用变量,用'$'引用变量,如:$variableName${variableName}(花括号限定边界)。
  3. 重定义变量,直接定义即可。
  4. 取消变量定义,用unset,如:unset variableName (不能删除只读变量)
  5. 只读变量,可以将已定义变量改为只读,如:readonly variableName
  6. 显示所有本地shell变量,用set命令即可。

变量类型:

  1. 局部变量
  2. 环境变量
  3. shell特殊变量(命令行参数是特殊变量)

命令行参数

  1. 引用命令行参数
  • $0 当前脚本的文件名
  • $# 传递给脚本或函数的参数个数(不包括$0)。
  • $* 传递给脚本或函数的所有参数(不包括$0)。
  • $@ 传递给脚本或函数的所有参数(不包括$0)。被双引号包含时,与$*稍有不同,下面将会单独讲到。
  • $? 上个命令的退出状态,或函数的返回值。成功返回0,否则非零(一般为1)。
  • $$ 当前进程的ID
  • $n 传递给脚本或函数的第n个参数。例如,第一个参数是$1,第二个参数是$2

如果获取第十个及其後的命令行参数,用${n}的形式。

  1. $*$@的区别:

前者将参数当做一个整体,後者将参数作为个体连接。所以,$*$@是一样的,而"$*""$@"不一样。

  1. IFS分隔符:

    "$\*" is equivalent to "$1c$2c...", where c is the first character
    of the value of the IFS variable. "$@" is equivalent to "$1" "$2" ...

IFS variable(内部分隔符),有'\040'(空格),'\011'(制表符),'\012'(换行符)。

  1. ${*}${@}的变量替换:

此两者的区别在变量替换,在${ }中使用*@是有区别的:
使用@得到结果列表,使用*得到一个字符串,如:

  • ${@:0} 得到$0 $1 $2 ... 的结果列表
  • ${*:0} 得到$0 $1 $2 ... 的单个字符串

注意,@*在花括号里面进行某些高级替换(见字符串操作)时包括$0,而$@(或${@})和$*(或${*})不包括。

变量引用时判断

注:加星号的行表示其他情况正常引用变量值。

引用方式 说明
${var-DEFAULT} 未声明则取默认值*
${var:-DEFAULT} 未声明或值空则取默认值*
${var=DEFAULT} 未声明则赋默认值*
${var:=DEFAULT} 未声明或值空则赋默认值*
${var+OTHER} 已声明则取他值否则取空值
${var:+OTHER} 已声明且值非空则取他值否则取空值
${var?ERR_MSG} 未声明则打印出错信息*
${var:?ERR_MSG} 未声明或值空则打印出错信息*
${!varname} 间接引用,以值为访问变量名称的变量引用其值
${!prefix*}${!prefix@} 扩展为以前缀开头的变量名,以IFS变量的第一个字符分隔
${!name[*]}${!name[@]} 扩展为数组中已声明元素的索引,以IFS变量的第一个字符分隔

以上标识符(var,DEFAULT,OTHER,ERR_MSG,varname,prefix,name)可以是任意的,其中,大写的(DEFAULT,OTHER,ERR_MSG)可以是字符串。

字符串

  • 字符串可以用单引号、双引号或不用引号括起来。
  • 单引号字符串,里面任何字符都照原样且不能出现单引号(转义也不可)。
  • 双引号字符串,里面可以引用变量,可以使用转义。
  • 无引号字符串,变量的值、参数等等都可以看为字符串。
  1. 字符串操作:
操作 方式 说明
串长 ${#string}
串址子串 ${string:position} 从position开始的子串
串取子串 ${string:position:length} 从position开始length长度的子串
删除首部最短匹配 ${string#substring} 从首部匹配子串(有多个匹配则取最短的)并删除
删除首部最长匹配 ${string##substring} 从首部匹配子串(有多个匹配则取最长的)并删除
删除尾部最短匹配 ${string%substring} 从尾部匹配子串(有多个匹配则取最短的)并删除
删除尾部最长匹配 ${string%%substring} 从尾部匹配子串(有多个匹配则取最长的)并删除
替换第一个子串 ${string/substring/replacement} 查找第一个匹配的子串并替换之
替换所有子串 ${string//substring/replacement} 查找所有匹配的子串并依次替换之
前缀替换 ${string/#substring/replacement} 若前缀匹配则替换之
後缀替换 ${string/%substring/replacement} 若後缀匹配则替换之

注:string是变量名,position是子串位置(为0则串首,可为负数但要小括号括起来,为-1则串尾),substring可为正则表达式。

  1. 字符串大小写转换

以下在bash4.0+被支持:

  • 首字符转大写 ${string^}
  • 所有字符转大写 ${string^^}
  • 首字符转小写 ${string,}
  • 所有字符转小写 ${string,,}

解注:
${parameter^pattern} ${parameter^^pattern}
${parameter,pattern} ${parameter,,pattern}
扩展parameter值并转换其英文字母的大小写(parameter变量不会改变)。
pattern可省略,等同于'?'(匹配每一个字符)。注意,pattern必须是一个字符而非字符串。
'^'操作符将匹配pattern的小写字母转换为大写;
','操作符将匹配pattern的大写字母转换为小写。
"^^"",,"则在扩展后的值中转换每一个匹配的字符,'^'','操作符只匹配首字符并处理。
若parameter是'@''*',则此操作依次作用于每个位置参数并得到结果列表。
若parameter是数组变量且下标是'@''*',则此操作依次作用于数组所有成员并得到结果列表。

数组

  • 数组定义:array_name=(value1 ... valuen)
    括号里面允许换行,value以空格或换行分隔
  • 也可以单独定义数组各个分量:array_name[i]=valuei (i可以是数字).
    即数组可以不定义就使用,也可以定义时赋初值,数组元素就像普通变量一样使用即可。
  • 引用数组所有元素:${array_name[*]}${array_name[@]}.
    只会取得数组已定义元素值的列表
  • 可以使用unset删除数组元素:unset array_name[i]
  • 使用${#array_name[*]}${#array_name[@]}取得数组已定义的元素数量(数组长度)
  • 使用${#array_name[n]}取得n号元素的长度,这些就像求串长一样。
  • 数组切片:${array_name[*]:position:length}${array_name[@]:position:length}.
    这其实是对数组已定义元素值的列表进行切片,就像串取子串一样。

注:就是说之前提到的字符串操作实际上是视字符串为列表的操作,同样能作用于数组构建的列表。

二、shell语句

注释语句

'#'开头的行是注释,没有类似C/C++语言的多行注释。

条件语句

有三种格式:

  1. if [ expression ] then ... fi
  2. if [ expression ] then ... else ... fi
  3. if [ expression ] then ... elif [ expression2 ] then ... else ... fi

可以写成一行也可以多行,如:

if test $[num1] -eq $[num2]; then echo 'equal'; elif test $[num1] -lt $[num2]; then echo 'less'; else echo 'greater'; fi;

或者:

1
2
3
4
5
6
7
8
9
if test $[num1] -eq $[num2]
then
echo 'equal'
elif test $[num1] -lt $[num2]
then
echo 'less'
else
echo 'greater'
fi

循环语句

  1. for循环:

     for var in list
     do
       command...
     done

    for循环还支持类C语言格式:

     for (( expr1 ; expr2 ; expr3 )) ; do commands ; done
  2. while循环:

     while command
     do
       Statements to be executed if command is true(return 0).
     done
  3. until循环:(和while循环测试条件正好相反)

     until command
     do
       Statements to be executed until command is true(return 0).
     done

分支语句

类似C语言的switch-case语句,shell里面有case-esac格式的分支语句:

case var in
mode1)
  command...
  ;;
mode2)
  command...
  ;;
*)
  command...
  ;;
esac

以上分支语句,依次匹配var于mode1,mode2,*,mode是正则表达式,支持'*','?','[abc]','[a-n]'字符和'|'或运算,";;"必须有且在里面表示break(跳出case语句)。

选择项语句

select name in list
do 
  statements that can use  $name... 
done

产生list列表中的菜单项供选择(以数字键选择),然後循环执行do-done之间的语句,在循环中可以用break跳出。另外,select命令使用PS3提示符,默认为"#? ",可以用PS3='string'来改变提示字符串。select语句可以搭配case语句。

函数

shell函数很重要,由于shell逐行执行,shell 函数必须先定义后使用。

[function] funname [()] # function关键字和"()"必须有一个
{
  command;
  [return int;]
} # 花括号可以换成小括号,区别见特殊字符
  • 以上方括号"[]"中代表可选内容。
  • 如果不用return关键字则以最後一条命令的运行结果作为返回值(0-255之间)。
  • shell函数可以传参,就像命令一样使用函数,就像脚本一样编写函数。
  • 函数里面可以定义全局变量(默认)和局部变量(local修饰,如:localvar=value)。
    • local变量的作用域
      local变量一般只能在函数中定义,它在函数体中有效且会屏蔽同名全局变量,即使是函数中调用的子函数也能访问到该变量,所以一般在函数中定义变量都应该加上local关键字。

通用退出状态码

exit code 说明
0 命令成功结束
1 通用未知错误 
2 误用Shell命令
126 命令不可执行
127 没找到命令
128 无效退出参数
128+x Linux信号x的严重错误
130 命令通过Ctrl+C终止
255 退出状态码越界

三、shell运算

shell是命令式语言(实际上bash內建了一些命令或扩展来支持运算)。

算术扩展

((expression)) # 可以用$((expression))引用表达式的值,也可以用$[expression]
  • 表达式值零则返回1否则返回0(退出状态码,0表示true,1表示false)。

  • expression计算整数运算,写法和C语言的表达式一样(支持同样的运算符), 此外还支持:

    1. "**" 指数运算
    2. '#' 进制数,如:
      "base#n"表示以base为进制的数n,base范围[2,64],数字以此取[0,9],[a,b],[A,B],'@','_'.

expr命令

expr命令可以实现数值计算、字符串操作,这里只讲有关数值计算(整数运算)的内容:

需注意,不能直接使用关键字和特殊字符(需用''转义),用参数传递表达式,然後expr命令输出表达式值,所以参与运算的参数要用空格分开,运算符也要分开。

支持的运算符:

  1. 数值'+','-','*','/','%',"()".
  2. 数值或字符串比较'<',"<=",'=',"==","!=",">=",'>'.

注意,以上运算符中,'*'需转义,写作"\*",同样需要转义的字符有:'(',')','<','>'.

[命令和test命令

[命令是bash内建命令,和test命令作用是等同的,'['调用test命令标识,']'关闭条件判断,因为是命令,所以参数要隔开。

选项 说明
'=' 字符串相等
"!=" 字符串不等
"==" '='
'>' 字符串ASCII码大于
'<' 字符串ASCII码小于
-z 串长是否值零
-n 串长是否非零
[ str ] 字符串是否非空
-eq 数值相等
-ne 数值不等
-gt 数值大于
-lt 数值小于
-ge 数值大于等于
-le 数值小于等于
'!' 逻辑非
-o 逻辑或
-a 逻辑与
-b 文件是否块设备
-c 文件是否字符设备
-d 文件是否目录
-f 文件是否普通文件
-g 文件是否设置了SGID位
-k 文件是否设置了粘着位
-p 文件是否有名管道
-u 文件是否设置了SUID位
-r 文件是否可读
-w 文件是否可写
-x 文件是否可执行
-s 文件是否非空(文件大小非零)
-e 文件是否存在

注意:字符串比较时最好加双引号,以免空字符串导致错误而可能被解析为[ str ]类型。

[[关键字

[[是bash的关键字而非命令(它不是posix通用的),在[[]]之间不进行文件名展开和分词,但会参数扩展和命令替换。

  • 文件名展开(filename expansion),例如,*展开为当前工作目录下所有文件。
  • 分词(word splitting),例如,一个带空格的参数可能被分割成多个参数。

[[作用与[作用基本一样,不同的是:

[ ]使用 - [[ ]]使用 -
-a -o && ||
< > < >

注意:别滥用比较运算符(不可用于数值比较)。

数学运算符只能在[[ ]]中使用,有:'+','-','*','/','%',另外在[[ ]]中还有:

  • 当使用==!=操作符时,操作符右边的字符串被用作模式并且执行一个匹配。
  • 当使用=~操作符时,操作符右边的字符串被当作正则表达式来进行匹配。
  • 由于没有文件名展开和分词,所以在字符串比较时可以不加双引号引用变量。

let命令

let expression

基本等价于((expression)),但有少许区别:
let用空格分隔多个表达式,而(( ))用逗号分隔。

declare -i

通过"declare -i",可以定义整型变量,这样就能进行'+','-','*','/','%'运算了(要求参与运算的都是整型变量并且赋值给整型变量),如:

declare -i m n ret
m=10
n=30
ret=$m*$n+m-n # 可以直接用运算符,直接用变量
echo $ret

bc命令

bc是Linux下的计算器,一般用如下格式:

ret=`echo expression | bc`

如:ret=`echo sqrt\($a\) | bc` 或 ret=`echo "scale=10;sqrt($a)" | bc`

bc计算器可以进行很多种运算,支持浮点运算。

四、shell特殊字符

转义字符

转义字符 说明
'\a' 响铃(BEL)
'\b' 退格(BS),将当前位置移到前一列
'\c' 取消其及之後的所有字符(包括换行符)
'\f' 换页(FF),将当前位置移到下页开头
'\n' 换行(LF),将当前位置移到下一行开头
'\r' 回车(CR),将当前位置移到本行开头
'\t' 水平制表符(HT)
'\v' 垂直制表符(VT)
'\\' 反斜杠字符

'\ ','\(','\)','\[','\]','\{','\}'等等,用于使之不做特殊用途。

注意'\c'要配合"echo -e"使用才会转义生效,,一般用于echo取消换行符。

输入输出重定向

文件描述符,除stdin(0),stdout(1),stderr(2)外,用户可定义3~max的文件描述符,可以通过文件描述符重定向输入输出。

  1. 输出重定向符:'>'
  • '>' 覆盖输出
  • ">>" 追加输出
  • command > filename 把标准输出重定向到一个文件中(覆盖)
  • command > filename 2>&1 把标准输出和错误一起重定向到一个文件中(覆盖)
  • command 2 > filename 把标准错误重定向到一个文件中(覆盖)
  • command 2 >> filename 把标准输出重定向到一个文件中(追加)
  • command >> filename 2>&1 把标准输出和错误一起重定向到一个文件(追加)
  1. 输入重定向符:'<'
  • command [fd] < file 或文件描述符或设备(fd是文件描述符,此处默认为0) 还有:

  • '<<<' 字符串输入,如:cat <<< "string"

  • '<<' 文档输入,如:

    cat << EOF > /tmp/file
    hello
    $name
    EOF

    给第一个EOF加上双引号,文档内容就不会有变量替换了。

  1. 管道符:'|'
  • 'command1 | command2' 左边命令标准输出作为右边命令标准输入
  • 'command1 |& command2' 左边命令标准输出和标准错误作为右边命令标准输入

注意:管道符两边的命令会作为子Shell执行,而子Shell无法更改父环境的变量。

高级重定向

  • exec命令可以更改标准输入输出文件描述符并影响接下来执行的命令。

    exec > file # 更改标准输出到文件
    exec < file # 更改标准输入自文件
  • exec命令还可以打开一个文件:

    exec N<> FILENAME # 重定向符也可使用'<','>'分读写打开
  • 关闭文件:

    exec N<&- 或 exec N>&- # exec可省略
  • 文件描述符复制:(文件描述符N复制到M)

    [exec] [M]>&N #省略[M]则默认为1
    [exec] [M]<&N #省略[M]则默认为0

    []中的是可选项,exec可换成其他命令。

  • 文件描述符移动:(文件描述符N移动到M)

    [exec] [M]>&N- #省略[M]则默认为1,等价于:[exec] [M]>&N;N>&-
    [exec] [M]<&N- #省略[M]则默认为0,等价于:[exec] [M]<&N;N<&-

变量替换

引用方式 说明
$varname 引用变量
${ } 变量替换,详见shell变量
$( ) 命令替换,等同于` `
$(( )) 替换为表达式值
$[ ] 等同于$(( ))

其他特殊字符

符号 说明
. 点号,文件包含符,作用同source命令,在目录操作中,也表示当前目录,两个点号则表示上一级目录
# 井号,单行注释,首行#!表示shell类型,或者是在变量替换中有特殊作用
~ 波浪号,帐户的home目录,等价于$HOME
; 分号,分隔连续命令
;; 连续分号,从case语句中跳出
' 单引号,表示字符串,里面任何字符都照原样且不能出现单引号
" 双引号,表示字符串,里面可以引用变量,可以使用转义
` 反引号,命令替换,用于获取其中的命令输出,`command`等价于$(command)
\ 反斜杠,转义字符
| 竖线,管道符,左边命令标准输出作为右边命令标准输入
: 冒号,bash内建命令,仅返回状态值0
? 问号,文件名展开中代表任意一个字符
* 星号,文件名展开中代表任何字符串
$ 钱号,变量替换
( ) 小括号,命令群组(会生成子shell),在'$( )'中做命令替换,用于定义函数,还用于定义数组
(( )) 双小括号,bash的算术扩展,在变量替换中引用表达式值,还用于C语言风格的for循环
{ } 大括号,命令群组(不生成子shell),相当于匿名函数;在文件名展开中做通配符扩展
[ ] 方括号,等同test命令,或在'$[ ]'中等价于'$(( ))',或者是在通配符中匹配范围内一个字符
[[ ]] 双方括号,[[关键字,用于某些运算
|| shell逻辑或,从左向右执行,直到命令返回成功或执行完毕
&& shell逻辑与,从左向右执行,直到命令返回失败或执行完毕
! shell逻辑非,将命令返回值取非,若command返回0则! command返回1,否则! command返回0
& 与号,置于命令结尾,表示将该命令置于後台工作,或者是在文件描述符中有特殊作用
^ 在通配符中表示匹配范围之外,如:[^0-9]将不匹配数字,注意,通配符不被bash展开但被某些命令接受
> >> 输出重定向
< << <<< 输入重定向
- 减号,在某些命令中表示标准输入或输出流
<(command) 进程替换,command命令置于後台异步写入到一个虚拟文件,<(command)被替换为该虚拟文件名
>(command) 进程替换,command命令置于後台异步读取到一个虚拟文件,<(command)被替换为该虚拟文件名

文件名展开中的通配符扩展

大括号的作用,注意展开後得到的是结果列表,如:

{a,b,c}{1,2} 展开为 a1 a2 b1 b2 c1 c2
{1..5} 展开为 1 2 3 4 5

五、shell命令

  • echo 屏幕输出

  • printf 格式输出(类似C语言的printf函数,format_string的含义都一样)

    printf format_string [arguments...] # 如:printf %d $var1 123

    与C语言的printf函数区别如下:

    • printf 命令不用加括号
    • format_string 可以没有引号,但最好加上,单引号双引号均可。
    • 参数多于格式控制符%时,format_string可以重用,可以将所有参数都转换。
    • arguments 使用空格分隔,不用逗号。
  • source 文件包含,如:source filename.sh

  • shift 删除参数列表中前n个参数,默认删除第一个参数

  • bash 打开一个bash环境,可用于执行脚本,例如:
    curl -L $url | bash -s args 获取网络上的脚本以args为参数执行,又或者:
    bash -c "$(curl -L $url)" @ args 亦同,获取脚本并执行,@作为脚本参数$0


Linux Shell精简教程
https://blog.siantao.top/手册/Shell/Linux Shell精简教程/
作者
玉水仙楊
发布于
2020年12月1日
许可协议