Shell脚本入门知识点总结

pdf版本笔记的下载地址: Shell脚本编程入门(访问密码:3834)


Shell是一个面向字符串编程的脚本语言.有两个很好的入门Shell教程: Shell 教程-菜鸟教程, Shell编程-C语言中文网.

一个shell脚本的示例

创建一个脚本的过程分为3步:

  1. 创建一个Shell脚本文件hello.sh(建议脚本文件以.sh结尾),内容如下:

    #!/bin/bash
    echo "Hello World !"
    

    第一句的#!符号定义了执行脚本的解释器为/bin/bash.

  2. 赋予用户执行该脚本的权限:

    chmod u+x hello.sh      # 赋予拥有者执行该脚本的权限
    
  3. 执行该脚本

    ./hello.sh  
    

    可以看到屏幕输出Hello World !.

    在执行脚本时,一定要加上./,表示执行当前目录下的hello.sh文件;否则系统会到PATH路径下寻找名为hello.sh的脚本并执行.

变量

自定义变量

定义和使用变量

在Shell脚本中,使用变量名=值定义变量(在定义变量时等号=左右两边不能有空格),使用$变量名${变量名}引用变量.

url1=http://www.baidu.com
echo $url1      # 输出 http://www.baidu.com
url2='http://www.baidu.com'
echo $url2      # 输出 http://www.baidu.com
url3="http://www.baidu.com"
echo $url3      # 输出 http://www.baidu.com

在Shell中,除数字以外所有的变量默认都被视为字符串,不论是否显示地使用引号(""'')包围变量的值.其中单引号'"是不同的:

  • 以单引号''包围的字符串,不会解析其中的变量和命令,字符串中的任何内容都会原样输出.
  • 以双引号""包围的字符串,会先解析其中的变量和命令,将执行结果替换到字符串内容中.
#!/bin/bash

name="chenhai"
echo 'my name is ${name}'   # 输出 my name is ${name}
echo "my name is ${name}"   # 输出 my name is chenhai
echo my name is ${name}     # 输出 my name is chenhai

echo 'today is `date`'      # 输出 today is `date`
echo "today is `date`"      # 输出 today is Sun Dec  8 06:Sun Dec  8 06:11:11 UTC 2019`date`

只读变量

使用readonly关键字可以将变量定义为只读变量,只读变量的值不能被改变.

#!/bin/bash
url="http://www.google.com"
readonly url
url="http://www.baidu.com"  # 修改只读变量的值会报错: This variable is read only.

执行上述程序,结果如下:

/bin/sh: NAME: This variable is read only.

删除变量

使用unset命令可以删除变量.

#!/bin/bash
url="http://www.google.com"
unset url
echo ${url}

运行上述脚本没有输出.

定义和使用数组

Shell数组不要求数组中元素类型均相同,有两种方式定义数组:

  1. 整体定义:

    array_name=(value0 value1 ... valuen)
    
  2. 单独定义

    array_name[0]=value0
    array_name[1]=value1
    ...
    array_name[n]=valuen
    

使用${array_name[n]}可以引用数组array_name的第n个元素,使用@*占位符索引数组表示取数组的所有元素.

#!/bin/bash
nums=(29 100 'www.baidu.com')
echo ${nums[@]}     # 输出 29 100 www.baidu.com

nums[10]=66         # 给第10个元素赋值(此时会增加数组长度)
echo ${nums[*]}     # 输出 29 100 www.baidu.com 66
echo ${nums[2]}     # 输出 www.baidu.com
echo ${nums[4]}     # 输出 空行

特殊变量

接收命令行参数的位置变量

位置变量常用于接收从命令行传递过来的参数.$0表示脚本文件名,$<num>表示第num个传入的参数.

下面创建一个脚本test.sh,内容如下:

echo "script name is $0"
echo "argument 1 is $1"
echo "argument 2 is $2"
echo "argument 3 is $3"

赋予当前用户执行该脚本的权限并在命令行中调用该脚本并传入参数:

./test.sh 192.168.0.1 8080 "hello world"

得到输出如下:

script name is 192.168.0.1
argument 1 is 192.168.0.1
argument 2 is 8080
argument 3 is hello world

其它特殊变量

变量含义
$0当前脚本文件名
$num传递给脚本的第num个参数
$#传递给脚本的参数个数
$*传递给脚本的所有参数(将所有的参数视为一个单词)
$@传递给脚本的所有参数(将所有的参数视为多个单词)
$?上一个命令的退出状态.0表示执行成功,其他值表示执行失败
$$当前脚本的进程号

下面例子展示$*$@的区别:

创建脚本test.sh,内容如下:

#!/bin/bash

echo "共传递给脚本$#个参数"

echo '演示使用$*遍历参数: 所有参数被视为一个单词'
for i in "$*"; do
    echo $i
done

echo '演示使用$@遍历参数: 所有参数被视为多个单词'
for i in "$@"; do
    echo $i
done

赋予当前用户执行该脚本的权限并在命令行中调用该脚本并传入参数:

./test aa bb 33 44

得到输出如下:

共传递给脚本4个参数

演示使用$*遍历参数: 所有参数被视为一个单词
aa bb 33 44

演示使用$@遍历参数: 所有参数被视为多个单词
aa
bb
33
44

命令替换: 将命令的输出结果赋值给变量

命令替换是指将命令的输出结果赋给某个变量,有两种形式可以完成命令替换,一种是反引号`,另一种是$().

variable=`commands`
variable=$(commands)

使用命令替换时最好将变量用双引号""包围,否则在多行输出时易发生混乱:

#!/bin/bash
LSL=`ls -l`
echo $(ls -l)       # 不使用双引号包围变量
echo "--------"     # 输出分隔符
echo "$(ls -l)"     # 使用引号包围变量

运行上述程序,得到输出如下:

total 8 drwxr-xr-x. 2 root root 21 7月 1 2016 abc -rw-rw-r--. 1 chenhai chenhai 147 10月 31 10:29 demo.sh -rw-rw-r--. 1 chenhai chenhai 35 10月 31 10:20 demo.sh~
--------
total 8
drwxr-xr-x. 2 root     root    21 7月   1 2016 abc
-rw-rw-r--. 1 chenhai chenhai 147 10月 31 10:29 demo.sh
-rw-rw-r--. 1 chenhai chenhai  35 10月 31 10:20 demo.sh~

反引号`$()在大多数情况下是等价的,但是$()支持多层嵌套,且可读性更好.但$()仅在Bash Shell中有效.

输入输出

标准输入输出

read命令

使用read命令从标准输入读取一段内容并将其赋值给变量.read命令的用法为:

read [-options] [variables]

variables表示存储数据的变量,options表示选项,常用选项如下:

选项说明
-a <array>把读取的数据赋值给数组array
-d <delimiter>用字符串delimiter指定读取结束的位置,而不是一个换行符(读取到的数据不包括 delimiter
-e在获取用户输入的时候,对功能键进行编码转换,不会直接显式功能键对应的字符
-n <num>读取num个字符,而不是整行字符
-p <prompt>显示提示信息,提示内容为prompt
-r原样读取(Raw mode),不把反斜杠字符解释为转义字符
-s静默模式(Silent mode),不会在屏幕上显示输入的字符.当输入密码和其它确认信息的时候,这是很有必要的
-t <seconds>设置超时时间,单位为秒.如果用户没有在指定时间内输入完成,那么 read 将会返回一个非 0 的退出状态,表示读取失败.
-u <fd>使用文件描述符fd作为输入源,而不是标准输入,类似于重定向

下面程序要求使用者在20秒内输入密码:

#!/bin/bash
if
    read -t 20 -sp "Enter password in 20 seconds(once) > " pass1 && printf "\n" &&  #第一次输入密码
    read -t 20 -sp "Enter password in 20 seconds(again)> " pass2 && printf "\n" &&  #第二次输入密码
    [[ $pass1 == $pass2 ]]  	#判断两次输入的密码是否相等
then
    echo "Valid password"
else
    echo "Invalid password"
fi

在这里使用&&组合命令,若其中一条不满足,则退出.

echo命令

使用echo输出时默认会换行,可以使用-n参数使命令不换行.

使用echo输出时默认不对反斜杠\开头的字符进行转义,可以使用-e参数开启转义.开启转义后也可以使用\c字符控制不换行.

printf命令

printf命令模仿C语言中的printf()函数,其用法如下:

printf  format-string  [arguments...]

其中的format-string与C语言中的格式控制字符串大同小异.

#!/bin/bash
 
# format-string为双引号
printf "%d %s\n" 1 "abc"

# 单引号与双引号效果一样 
printf '%d %s\n' 1 "abc" 

# 没有引号也可以输出
printf %s abcdef

# 格式只指定了一个参数,但多出的参数仍然会按照该格式输出,format-string 被重用
printf %s abc def
printf "%s\n" abc def
printf "%s %s %s\n" a b c d e f g h i j

# 如果没有 arguments,那么 %s 用NULL代替,%d 用 0 代替
printf "%s and %d \n" 

输入输出重定向

文件描述符

在Linux系统中,一切皆文件,输入输出设备都是以文件的形式被对待的,Linux命令运行时会打开3个文件.

文件描述符文件名类型硬件
0stdin标准输入文件键盘
1stdout标准输出文件显示器
2stderr标准错误输出文件显示器

输出重定向

可以使用>(覆盖)或>>(追加)将标准输出重定向到文件中,可以通过给出文件描述符指定将标准输出文件标准错误输出文件重定向到文件中(默认文件描述符为1,只重定向标准输出).

类型命令作用
标准输出重定向command >file以覆盖的方式,将command的正确结果输出到文件file
标准输出重定向command >>file以追加的方式,将command的正确结果输出到文件file
标准错误输出重定向command 2>file以覆盖的方式,将command的错误结果输出到文件file
标准错误输出重定向command 2>>file以追加的方式,将command的错误结果输出到文件file
标准输出和标准错误输出同时重定向command &>filecommand >&file
command >file 2>&1command >file 2&>1
以覆盖的方式,将command的正确结果和错误结果都输出到文件file
标准输出和标准错误输出同时重定向command 1>file1 2>file2以覆盖的方式,将command的正确结果输出到文件file1中,错误结果输出到文件file2
标准输出和标准错误输出同时重定向command &>>file以追加的方式,将command的正确结果和错误结果都输出到文件file
标准输出和标准错误输出同时重定向command 1>>file1 2>>file2以追加的方式,将command的正确结果输出到文件file1中,错误结果输出到文件file2

输入重定向

不再使用键盘作为命令输入的来源,而是使用文件作为命令的输入.

符号说明
command <filefile文件中的内容作为command的输入
command <<END从标准输入(键盘)中读取数据,直到遇见分界符END才停止(分界符可以是任意的字符串,用户自己定义)
command <file1 >file2file1作为command的输入,并将command的结果输出到file2

和输出重定向类似,输入重定向的完整写法是fd<file,其中fd表示文件描述符,如果不写,默认为0.

fd<file

下面程序中使用文件readme.txt作为while子句的输入:

while read str; do
    echo $str
done <readme.txt

Shell运算符

Shell的运算符很复杂,有一些运算符或者命令存在着各种各样的缺点.因此,推荐使用的运算符如下:

  • 进行算术运算时,使用运算符(()).
  • 在进行逻辑运算时,使用运算符[[]].

算数运算

与其他编程语言不同,Shell不能直接进行算数运算,必须使用数学计算命令.下面是一个反例:

echo 2+8    # 输出 2+8

a=23
b=$a+55
echo $b     # 输出 23+55

b=90
c=$a+$b
echo $c     # 输出 23+90

Shell中常用的数学运算符有两个,一个是expr,另一个是(( ))[],他们都只能进行整数运算.其中expr比较繁琐,推荐使用(())[].要想进行小数运算,需要借助bc命令.

Shell中支持的算术运算符如下:

算术运算符说明/含义
+,-加法(或正号)、减法(或负号)
*,/,%乘法、除法、取余(取模)
**幂运算
++,--自增和自减,可以放在变量的前面也可以放在变量的后面
!,&&,||逻辑非(取反)、逻辑与(and)、逻辑或(or)
<,<=,>,>=,!=比较符号(小于、小于等于、大于、大于等于)
<<,>>向左移位、向右移位
~,|,&,^按位取反、按位或、按位与、按位异或
=,+=,-=,*=,/=,%=赋值运算符
#!/bin/bash

a=10
b=20
c=30

echo '(a+b)=' $((a+b))  # 输出 (a+b)= 30
echo '(a-b)=' $((a-b))  # 输出 (a-b)= -10
echo '(a*b)=' $((a*b))  # 输出 (a*b)= 200
echo '(a/b)=' $((a/b))  # 输出 (a/b)= 0
echo '(a%b)=' $((a%b))	# 输出 (a%b)= 10
echo '(a**b)=' $((a*b))	# 输出 (a**b)= 7766279631452241920
echo '(~a)=' $((~a))  	# 输出 (~a)= -11

echo '(a==b)=' $((a=b)) # 输出 (a==b)= 0
echo '(a>b)=' $((a>b))  # 输出 (a>b)= 0
echo '(a<b && b<c)=' $((a>b && b<c))  # 输出 (a<b && b<c)= 0

((c*=2))
echo 'c=' $c        # 输出 c= 60

关系运算符

使用test命令可以进行关系运算,test命令可以测试三类对象: 字符串,整数,文件属性.测试结果为真时返回0,为假时返回1.

test命令也可以简写为[],或使用[[]]关键字替代,使用[][[]]时必须在括号与表达式之间加空格.下面两种写法是等价的:

test expression
[ expression ]      # 使用[]时必须在括号与表达式之间加空格
[[ expression ]]    # 使用[[]]时必须在括号与表达式之间加空格

test命令(或简写为[])本质上是命令,为避免空参数的问题,应尽量将参数用双引号""包围起来,也需要注意转义的问题(如>应写为\>).而[[]]是Shell内置的关键字,不需要担心空参数和转义的问题.因此,在进行关系运算时,应尽量使用[[]].

数值比较

运算符说明
$num1 -eq $num2判断 $num1$num2是否相等
$num1 -ne $num2判断 $num1$num2是否不相等
$num1 -gt $num2判断 $num1是否大于$num2
$num1 -lt $num2判断 $num1是否小于$num2
$num1 -ge $num2判断 $num1是否大于等于$num2
$num1 -le $num2判断 $num1是否小于等于$num2
#!/bin/bash

num1=10
num2=20

[[ $num1 -eq $num2 ]]; echo $?	# 输出 1
[[ $num1 -ne $num2 ]]; echo $?	# 输出 0
[[ $num1 -gt $num2 ]]; echo $?	# 输出 1
[[ $num1 -lt $num2 ]]; echo $?	# 输出 0
[[ $num1 -ge $num2 ]]; echo $?	# 输出 1
[[ $num1 -le $num ]]; echo $? 	# 输出 0

字符串比较

运算符说明
$str1 = $str2$str1 == $str2判断str1str2是否完全相同
$str1 != $str2判断str1str2是否不同
-z $str判断str的长度是否为0
-n $str判断str的长度是否不为0
$str1 > $str2判断str1是否大于str2
$str1 < $str2判断str1是否小于str2
str1="aaa"
str2="bbbb"

[[ $str1 = $str2 ]]; echo $?	# 输出 1
[[ $str1 == $str2 ]]; echo $?	# 输出 1
[[ $str1 != $str2 ]]; echo $?	# 输出 0
[[ -z $str1 ]]; echo $?     	# 输出 1
[[ -n $str1 ]]; echo $?     	# 输出 0
[[ $str1 > $str2 ]]; echo $?    # 输出 1
[[ $str1 < $str2 ]]; echo $?    # 输出 0

文件比较

  1. 文件类型判断

    运算符说明
    -e filename判断文件filename是否存在.
    -b filename判断文件filename是否存在,并且是否为块设备文件.
    -c filename判断文件filename是否存在,并且是否为字符设备文件.
    -d filename判断文件filename是否存在,并且是否为目录文件.
    -f filename判断文件filename是否存在,井且是否为普通文件.
    -L filename判断文件filename是否存在,并且是否为符号链接文件.
    -p filename判断文件filename是否存在,并且是否为管道文件.
    -s filename判断文件filename是否存在,并且是否为非空.
    -S filename判断文件filename是否存在,并且是否为套接字文件.
  2. 文件权限判断

    运算符说明
    -r filename判断文件filename是否存在,并且是否拥有读权限.
    -w filename判断文件filename是否存在,并且是否拥有写权限.
    -x filename判断文件filename是否存在,并且是否拥有执行权限.
    -u filename判断文件filename是否存在,并且是否拥有 SUID 权限.
    -g filename判断文件filename是否存在,并且是否拥有 SGID 权限.
    -k filename判断文件filename是否存在,并且是否拥有 SBIT 权限.
  3. 文件比较

    运算符说明
    filename1 -nt filename2判断filename1的修改时间是否比filename2
    filename -ot filename2判断filename1的修改时间是否比filename2
    filename1 -ef filename2判断filename1是否和filename2inode号一致,即判断两者是否为同一文件
    是一个判断硬链接的方法

逻辑运算

Shell还提供了逻辑运算符用于对比较结果进行逻辑运算.

运算符说明
-a
-o
!

对于[[]]运算符,不支持-a-o,而应该使用&&||.

流程控制语句

判断语句

if语句

if语句有如下3种形式:

  1. if语句

    if  condition
    then
        statement(s)
    fi
    

    也可以将ifthen写在一行:

    if  condition; then
    	statement(s)
    fi
    
  2. if-else语句

    if  condition; then
       statement1
    else
       statement2
    fi
    
  3. if-elif-else语句

    if condition1; then
       statement1
    elif condition2; then
        statement2
    elif condition3; then
        statement3
    else
       statementn
    fi
    

下面例子中的脚本接受一个路径并判断它代表的是文件还是目录:

#!/bin/bash

if [[ $# -ne 1 ]]; then
	echo "调用该命令的格式为: $0 <filename>"
fi


if [[ -f $1 ]]; then
	echo "路径$1是一个文件"
elif [[ -d $1 ]]; then
	echo "路径$1是一个目录"
else
	echo "路径$1是其他格式"
fi

case语句

case语句的语法如下:

case expression in
    pattern1)
        statement1
        ;;
    pattern2)
        statement2
        ;;
    pattern3)
        statement3
        ;;		# 相当于break
    *)			# 相当于default
        statementn
esac

其中pattern可以是数字,字符串或正则表达式.

其实最后的*)能用于匹配默认情况并不是什么语法规定,而是因为*在正则表达式中本来就能表示任意字符.

下面脚本接受一个字符并判断它的类别:

#!/bin/bash

if [[ $# -ne 1 ]]; then
	echo "调用该命令的格式为: $0 <character>"
fi

case $1 in
    [a-zA-Z])
		echo "$1是一个字符"
        ;;
    [0-9])
        echo "$1是一个数字"
        ;;
    [,.?!])
		echo "$1是一个符号"        
		;;
    *)
        echo "$1是其它字符"
esac

循环语句

while语句

while语句的语法如下:

while condition
do
    statements
done

下面脚本用于创建多个文件:

#!/bin/bash

i=0

while ((i<10))
do
	touch "file$i"
	((i++))
done

for语句

可以使用C风格的for循环语句或Python风格的for循环语句,语法分别如下:

  • C风格的for循环语句

    for ((exp1; exp2; exp3))
    do
        statements
    done
    

    下面脚本计算从1到100的加和:

    #!/bin/bash
    
    sum=0
    
    for ((i=1; i<=100; i++))
    do
        ((sum += i))
    done
    
    echo "The sum is: $sum"
    
  • Python风格的for循环语句

    for variable in value_list
    do
        statements
    done
    

    value_list的取值可以有以下几种情况:

    1. 直接给出的具体的值,多个值之间用空格分隔

      #!/bin/bash
      
      for n in 1 2 3 4 5 6
      do
          echo $n
      done
      
    2. 使用{start..end}给出的一个范围

      下面程序遍历1~100直接的整数

      #!/bin/bash
      
      for n in {1..100}
      do
          echo $n
      done
      

      下面程序遍历ASCII表上A~z间的所有字符

      #!/bin/bash
      
      for c in {A..z}
      do
          echo $c
      done
      

      输出ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_\`abcdefghijklmnopqrstuvwxyz

    3. 其他命令的输出结果

      下面程序遍历一个使用seq命令生成的等差数列

      #!/bin/bash
      
      for n in $(seq 1 2 20)
      do
          echo $n
      done
      

      下面程序遍历当前目录下所有.sh文件

      #!/bin/bash
      
      for filename in $(ls *.sh)
      do
          echo $filename
      done
      
    4. 使用通配符

      使用通配符,可以更方便的遍历当前目录下所有.sh文件

      #!/bin/bash
      
      for filename in *.sh
      do
          echo $filename
      done
      
    5. 使用特殊变量: 可以使用$@等特殊变量,事实上,若我们不显示指定value_list,我们便利的也是$@.

      #!/bin/bash
      
      function func(){
          for str in $@
          do
              echo $str
          done
      }
      
      func C++ Java Python C#
      

break和continue语句

与C语言不同的地方之处在于,breakcontinue语句都带有一个参数n,表示退出循环的层数,默认为1.

函数

函数的定义与调用

在Shell中,定义函数的语法如下:

[function] funname[()] {
    statements
    [return value]
}

可以像使用命令那样调用函数并传入参数

funname param1 param2 param3

与命令类似,在函数中使用$#得到参数个数,使用$*$@得到所有参数,使用$<num>引用第num个参数,使用#?得到上一行被调用的函数的返回值.

#!/bin/bash

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

pdf版本笔记的下载地址: Shell脚本编程入门(访问密码:3834)

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值