Ronan Blog

罗华东的博客 | 向前每多走一步,热爱和勇气就会多一分。

shell 编程

2024-09-02 6 min read Linux Ronan

一、运算符

1、算术运算符

下表列出了常用的算术运算符,假定变量 a 为 10,变量 b 为 20:

运算符说明举例
+加法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。[ $a ==$b ] 返回 false。
!=不相等。用于比较两个数字,不相同则返回 true。[ $a !=$b ] 返回 true。

注意: 条件表达式要放在方括号之间,并且要有空格,例如: [$a==$b] 是错误的,必须写成 [ $a ==$b ]

  • 乘号(*)前边必须加反斜杠()才能实现乘法运算;
  • if…then…fi 是条件语句,后续将会讲解。
  • 在 MAC 中 shell 的 expr 语法是: $((表达式)) ,此处表达式中的 “*” 不需要转义符号 “" 。

2、关系运算符

关系运算符只支持数字,不支持字符串,除非字符串的值是数字。

下表列出了常用的关系运算符,假定变量 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。

3、布尔运算符

下表列出了常用的布尔运算符,假定变量 a 为 10,变量 b 为 20:

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

4、逻辑运算符

以下介绍 Shell 的逻辑运算符,假定变量 a 为 10,变量 b 为 20:

运算符说明
&&逻辑的 AND
||逻辑的 OR

5、字符串运算符

下表列出了常用的字符串运算符,假定变量 a 为 “abc”,变量 b 为 “efg”:

运算符说明举例
=检测两个字符串是否相等,相等返回 true。[ $a =$b ] 返回 false。
!=检测两个字符串是否不相等,不相等返回 true。[ $a !=$b ] 返回 true。
-z检测字符串长度是否为0,为0返回 true。[ -z $a ] 返回 false。
-n检测字符串长度是否不为 0,不为 0 返回 true。[ -n “$a” ] 返回 true。
$检测字符串是否不为空,不为空返回 true。[ $a ] 返回 true。

6、文件测试运算符

文件测试运算符用于检测 Unix 文件的各种属性。

属性检测描述如下:

操作符说明
-b file检测文件是否是块设备文件,如果是,则返回 true。
-c file检测文件是否是字符设备文件,如果是,则返回 true。
-d file检测文件是否是目录,如果是,则返回 true。
-e file检测文件是否存在,如果是,则返回 true。
-f file检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。
-g file检测文件是否设置了 SGID 位,如果是,则返回 true。
-k file检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。
-p file检测文件是否是有名管道,如果是,则返回 true。
-u file检测文件是否设置了 SUID 位,如果是,则返回 true。
-r file检测文件是否可读,如果是,则返回 true。

其他检查符:

  • -S: 判断某文件是否 socket。
  • -L: 检测文件是否存在并且是一个符号链接。

二、字符串处理

1、字符串切片

{变量:偏移量:截取长度}

--------------------------example
[192.168.2.230] - root@ubuntu20.04.5-template:/ root
$ MYPATH=' /usr/bin/wget'

[192.168.2.230] - root@ubuntu20.04.5-template:/root
$ echo ${MYPATH: 4:4}
/bin     			  #其会左向右从第四位向后取四位(字符串第一位为0)

[192.168.2.230] - root@ubuntu20.04.5-template:/ root
$ echo ${MYPATH: -4}  #这会右向左从末尾向前取四位,「-4和冒号之间要有一个空格」

2、基于模式取子串

${var#*word}
其中word可以是指定的任意字符,自左而右,删除字符串开头至第一次出现word字符之间的所有字符。

${var##*word}
其中word可以是指定的任意字符,自左而右,删除字符串开头至最后一次出现word字符之间的所有字符。

${var%sword*}
其中word可以是指定的任意字符,自右而左,删除字符串尾部至第一次出现word字符之间的所有字符。

${var%%sword*}
其中word可以是指定的任意字符,自右而左,删除字符串尾部至最后一次出现word字符之间的所有字符

3、查找删除

${var/匹配文本}
删除第一次匹配到的内容

${var//pattern}
删除所有匹配到的内容

${var/#pattern}
删除行首匹配到的内容

${var/%pattern}
删除行尾匹配到的内容

4、查找替换

${var/匹配文本/替换文本}
替换第一次匹配到的内容

${var//匹配文本/替换文本}
替换所有匹配到的内容

${var/#匹配文本/替换文本}
行首匹配时替换行首

${var/%匹配文本/替换文本}
行尾匹配时替换行尾

三、条件判断语法格式

  • test条件表达式
  • [ 条件表达式 ]
  • [[ 条件表达式 ]]

特别说明

1)[ 亲亲,我两边都有空格,不空会被打死的喔~ ]

2)[[ 亲亲,我两边都有空格,不空会被打死的喔~ ]]

3)更多判断,man test查看,许多的参数都可用来进行条件判断

[] 和 [[]] 的区别:

[] (test 命令)

[] 是传统的测试条件的命令,也称为 test 命令。这种语法适用于POSIX标准的Shell,包括sh和bash。

  • 基本语法

    [ condition ]
    
  • 用法和特点

    • 条件需要用空格分隔。
    • 只支持基本的逻辑和比较操作。
    • 适用于大多数Unix系统。
  • 示例

    if [ "$a" -eq "$b" ]; then
      echo "a 等于 b"
    fi
    

[[]] (扩展测试命令)

[[]] 是Bash特有的扩展测试命令,提供了更强大的功能和更简洁的语法。

  • 基本语法

    [[ condition ]]
    
  • 用法和特点

    • 支持更复杂的条件测试,例如模式匹配(通配符)。
    • 可以直接使用逻辑运算符 &&|| 进行复合条件判断。
    • 允许在字符串比较中使用 =~ 操作符进行正则表达式匹配。
    • 不需要对条件中的变量进行双引号引用,因为它可以正确处理空字符串和带空格的字符串。
  • 示例

    if [[ $a == $b ]]; then
      echo "a 等于 b"
    fi
    
    if [[ $a =~ ^[0-9]+$ ]]; then
      echo "a 是一个数字"
    fi
    

详细对比

  • 字符串比较

    • [] 使用 = 进行字符串比较,通常需要使用双引号。
      [ "$a" = "$b" ]
      
    • [[]] 使用 == 进行字符串比较,不需要双引号。
      [[ $a == $b ]]
      
  • 逻辑运算

    • [] 需要使用 -a (AND) 和 -o (OR)。

      [ "$a" -eq "$b" -a "$c" -eq "$d" ]
      
    • [[]] 可以使用 &&||

      [[ $a -eq $b && $c -eq $d ]]
      
  • 正则表达式匹配

    • [] 不支持直接的正则表达式匹配。

    • [[]] 支持 =~ 操作符。

      [[ $a =~ ^[0-9]+$ ]]
      
  • 模式匹配

    • [] 不支持模式匹配。

    • [[]] 支持模式匹配。

      [[ $a == *.txt ]]
      

总结

  • 使用 [] 更适合于需要兼容POSIX标准的脚本和基本条件测试。
  • 使用 [[]] 提供了更强大和简洁的语法,适合于需要进行复杂条件判断的Bash脚本。

在实际编程中,建议在Bash脚本中尽量使用 [[]],除非需要兼容性考虑,这样可以充分利用其扩展功能和更清晰的语法。


四、流程控制

if else 语法格式

if condition
then
    command1 
    command2
    ...
    commandN
else
    command
fi

if else-if else 语法格式

if condition1
then
    command1
elif condition2 
then 
    command2
else
    commandN
fi

if else 的 […] 判断语句中大于使用 -gt,小于使用 -lt,其他用法请参照运算符

if [ "$a" -gt "$b" ]; then
    ...
fi

如果使用((…)) 作为判断语句,就可以使用类c风格判断,大于和小于可以直接使用 > 和 <。

if (( a > b )); then
    ...
fi

for循环

for var in item1 item2 ... itemN
do
    command1
    command2
    ...
    commandN
done

举个栗子🌰

#!/bin/bash

for str in This is a string
do
    echo $str
done

输出结果:

This
is
a
string

要打印1 -> 10的数字,可以用以下格式

#!/bin/zsh

for i in {1..10}
do 
    echo "$i"
done

或者以下类c风格

#!/bin/zsh

for ((i=1; i<11; i++))
do 
    echo "$i"
done

while循环

while condition
do
    command
done

举个栗子🌰

#!/bin/bash
int=1
while(( $int<=5 ))
do
    echo $int
    let "int++"
done

无限循环

无限循环语法格式:

while :
do
    command
done

或者

while true
do
    command
done

或者

for (( ; ; ))

until 循环

until 循环执行一系列命令直至条件为 true 时停止。

until 循环与 while 循环在处理方式上刚好相反。

一般 while 循环优于 until 循环,但在某些时候—也只是极少数情况下,until 循环更加有用。

until 语法格式:

until condition
do
    command
done

condition 一般为条件表达式,如果返回值为 false,则继续执行循环体内的语句,否则跳出循环。


五、获取当前日期以及时间

在 Unix 或 Linux 系统中,date 命令用于显示或设置系统日期和时间。date 命令可以接受多种格式化字符串来指定输出的格式。

命令 date +%M 会返回当前时间中的“分钟”部分。例如,如果当前时间是 14:35,那么运行 date +%M 会输出 35

下面是一些常用的 date 格式化字符串及其含义:

  • %Y:四位数的年份(例如,2023)
  • %m:两位数的月份(例如,05 表示五月)
  • %d:两位数的日期(例如,17)
  • %D:格式化日期(例如2024年五月17日,其会输出05/17/24)
  • %H:两位数的小时(24 小时制,例如,14 表示下午 2 点)
  • %M:两位数的分钟(例如,35)
  • %S:两位数的秒(例如,45)
  • %s:当前时间的Unix时间戳(也称为纪元时间)。Unix时间戳是从1970年1月1日00:00:00 UTC(协调世界时)开始经过的秒数。

所以,date +%M 的输出仅仅是当前时间的分钟部分。


六、传递参数

特殊参数

我们可以在执行 Shell 脚本时,向脚本传递参数,脚本内获取参数的格式为 $n,n 代表一个数字,1 为执行脚本的第一个参数,2 为执行脚本的第二个参数。

例如可以使用 $1、$2 等来引用传递给脚本的参数,其中$1表示第一个参数,$2 表示第二个参数,依此类推。

#!/bin/zsh脚本中,$#$0是特定的shell变量,用于脚本参数处理和信息获取。让我们详细解释它们的含义:

  • $# 用于表示传递给脚本的参数数量。
  • $0 为执行的文件名(包含文件路径),即脚本自身的文件名。
  • $?显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。
  • $-显示Shell使用的当前选项,与set命令功能相同。
  • $!后台运行的最后一个进程的ID号
  • $$脚本运行的当前进程ID号
  • $*以一个单字符串显示所有向脚本传递的参数。
  • $@与$*相同,但是使用时加引号,并在引号中返回每个参数。

$# 的含义

$# 代表传递给脚本的参数数量。在脚本开头,有以下代码:

if [ $# -ne 1 ]; then
    echo "Usage: $0 <directory>"
    exit 1
fi

这段代码的意思是:检查传递给脚本的参数数量是否不等于1。如果不是1个参数(即传递了0个参数或者多于1个参数),则输出使用说明并退出脚本。换句话说,$#用于确保脚本接收正好一个参数。

$0 的含义

$0 代表脚本自身的文件名(包含文件路径)。在脚本中同样的代码:

if [ $# -ne 1 ]; then
    echo "Usage: $0 <directory>"
    exit 1
fi

在这个例子中,$0表示脚本文件的名称。假设脚本的文件名是rename_files.sh,那么当你执行这个脚本时,$0就会被替换为rename_files.sh。因此,输出的使用说明会是:

Usage: rename_files.sh <directory>

常用命令

find命令

语法

find [路径] [匹配条件] [动作]

参数说明 : 路径 是要查找的目录路径,可以是一个目录或文件名,也可以是多个路径,多个路径之间用空格分隔,如果未指定路径,则默认为当前目录。

expression 是可选参数,用于指定查找的条件,可以是文件名、文件类型、文件大小等等。 匹配条件 中可使用的选项有二三十个之多,以下列出最常用的部份:

  • -name pattern:按文件名查找,支持使用通配符 * 和 ?。
  • -type type:按文件类型查找,可以是 f(普通文件)、d(目录)、l(符号链接)等。
  • -size [+-]size[cwbkMG]:按文件大小查找,支持使用 + 或 - 表示大于或小于指定大小,单位可以是 c(字节)、w(字数)、b(块数)、k(KB)、M(MB)或 G(GB)。
  • -mtime days:按修改时间查找,支持使用 + 或 - 表示在指定天数前或后,days 是一个整数表示天数。
  • -user username:按文件所有者查找。
  • -group groupname:按文件所属组查找。

动作: 可选的,用于对匹配到的文件执行操作,比如删除、复制等。

find 命令中用于时间的参数如下:

  • -amin n:查找在 n 分钟内被访问过的文件。
  • -atime n:查找在 n*24 小时内被访问过的文件。
  • -cmin n:查找在 n 分钟内状态发生变化的文件(例如权限)。
  • -ctime n:查找在 n*24 小时内状态发生变化的文件(例如权限)。
  • -mmin n:查找在 n 分钟内被修改过的文件。
  • -mtime n:查找在 n*24 小时内被修改过的文件。
    在这些参数中,n 可以是一个正数、负数或零。正数表示在指定的时间内修改或访问过的文件,负数表示在指定的时间之前修改或访问过的文件,零表示在当前时间点上修改或访问过的文件。

正数应该表示时间之前,负数表示时间之内。 例如:-mtime 0 表示查找今天修改过的文件,-mtime -7 表示查找一周以前修改过的文件。

关于时间 n 参数的说明:

  • +n:查找比 n 天前更早的文件或目录。
  • -n:查找在 n 天内更改过属性的文件或目录。
  • n:查找在 n 天前(指定那一天)更改过属性的文件或目录。

实例

查找当前目录下名为 file.txt 的文件:

find . -name file.txt

将当前目录及其子目录下所有文件后缀为 .c 的文件列出来:

find . -name "*.c"

将当前目录及其子目录中的所有文件列出:

find . -type f

如果后面的参数过多,可以用()括起来,但是注意使用 \ 转义符,以下命令的含义是找出当前目录以及子目录所有的.md和.txt文件

find . -type f \( -name "*.md" -o -name "*.txt" \)

read命令

read 内部命令被用来从标准输入读取单行数据。这个命令可以用来读取键盘输入,当使用重定向的时候,可以读取文件中的一行数据。

语法

read [-ers] [-a aname] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ...]

参数说明:

  • -a 后跟一个变量,该变量会被认为是个数组,然后给其赋值,默认是以空格为分割符。
  • -d 后面跟一个标志符,其实只有其后的第一个字符有用,作为结束的标志。
  • -p 后面跟提示信息,即在输入前打印提示信息。
  • -e 在输入的时候可以使用命令补全功能。
  • -n 后跟一个数字,定义输入文本的长度,很实用。
  • -r 屏蔽\,如果没有该选项,则\作为一个转义字符,有的话 \就是个正常的字符了。
  • -s 安静模式,在输入字符时不再屏幕上显示,例如login时输入密码。
  • -t 后面跟秒数,定义输入字符的等待时间。
  • -u 后面跟fd,从文件描述符中读入,该文件描述符可以是exec新开启的。

实例

1、简单读取

#!/bin/bash

#这里默认会换行  
echo "输入网站名: "  
#读取从键盘的输入  
read website  
echo "你输入的网站名是 $website"  
exit 0  #退出

测试结果为:

输入网站名: 
www.runoob.com
你输入的网站名是 www.runoob.com

2、-p 参数,允许在 read 命令行中直接指定一个提示。

#!/bin/bash

read -p "输入网站名:" website
echo "你输入的网站名是 $website" 
exit 0

测试结果为:

输入网站名:www.runoob.com
你输入的网站名是 www.runoob.com

3、-t 参数指定 read 命令等待输入的秒数,当计时满时,read命令返回一个非零退出状态。

#!/bin/bash

if read -t 5 -p "输入网站名:" website
then
    echo "你输入的网站名是 $website"
else
    echo "\n抱歉,你输入超时了。"
fi
exit 0

执行程序不输入,等待 5 秒后:

输入网站名:
抱歉,你输入超时了

4、除了输入时间计时,还可以使用 -n 参数设置 read 命令计数输入的字符。当输入的字符数目达到预定数目时,自动退出,并将输入的数据赋值给变量。

#!/bin/bash

read -n1 -p "Do you want to continue [Y/N]?" answer
case $answer in
Y | y)
      echo "fine ,continue";;
N | n)
      echo "ok,good bye";;
*)
     echo "error choice";;

esac
exit 0

该例子使用了-n 选项,后接数值 1,指示 read 命令只要接受到一个字符就退出。只要按下一个字符进行回答,read 命令立即接受输入并将其传给变量,无需按回车键。

只接收 2 个输入就退出:

#!/bin/bash

read -n2 -p "请随便输入两个字符: " any
echo "\n您输入的两个字符是:$any"
exit 0

执行程序输入两个字符:

请随便输入两个字符: 12
您输入的两个字符是:12

5、-s 选项能够使 read 命令中输入的数据不显示在命令终端上(实际上,数据是显示的,只是 read 命令将文本颜色设置成与背景相同的颜色)。输入密码常用这个选项。

#!/bin/bash

read  -s  -p "请输入您的密码:" pass
echo "\n您输入的密码是 $pass"
exit 0

执行程序输入密码后是不显示的:

请输入您的密码:
您输入的密码是 runoob

6.读取文件 每次调用 read 命令都会读取文件中的 “一行” 文本。当文件没有可读的行时,read 命令将以非零状态退出。 通过什么样的方法将文件中的数据传给 read 呢?使用 cat 命令并通过管道将结果直接传送给包含 read 命令的 while 命令。 测试文件 test.txt 内容如下:

123
456
runoob

测试代码:

#!/bin/bash
  
count=1    # 赋值语句,不加空格
cat test.txt | while read line      # cat 命令的输出作为read命令的输入,read读到>的值放在line中
do
   echo "Line $count:$line"
   count=$[ $count + 1 ]          # 注意中括号中的空格。
done
echo "finish"
exit 0

执行结果为:

Line 1:123
Line 2:456
Line 3:runoob
finish

使用 -e 参数,以下实例输入字符 a 后按下 Tab 键就会输出相关的文件名(该目录存在的):

$ read -e -p "输入文件名:" str 
输入文件名:a
a.out    a.py     a.pyc    abc.txt  
输入文件名:a