一文了解 Shell 脚本

shell 和 shell 脚本

Shell 是指一种应用程序,用户通过它可以访问操作系统内核的服务,如上图所示。而 Shell脚本(shell script)则是一种为 Shell 编写的脚本程序。

Shell 是一种应用程序,因此它的种类有很多,比如 Bourne Shell(/usr/bin/sh 或 /bin/sh)、Bourne Again Shell(/bin/bash)、C Shell(/usr/bin/csh)、K Shell(/usr/bin/ksh)、Shell for Root(/sbin/sh)等等。最常用的就是 Bourne Shell 和 Bourne Again Shell。

脚本执行

运行脚本的方法有两种:一种是作为可执行程序执行,一种是作为解释器参数执行。示例如下,下面是一个输出 Hello World ! 的脚本。

shell 复制代码
#!/bin/bash
echo "Hello World !"

可以看到在脚本的最前面,都有一个 #!, 它是一个约定的标记,它的作用是告诉系统使用哪一种 Shell 来执行脚本。

  • 作为可执行程序执行,会使用#!声明的shell来运行脚本
shell 复制代码
chmod +x ./test.sh  #使脚本具有执行权限
./test.sh  #执行脚本
  • 作为解释器参数执行,直接使用 /bin/sh 运行,会忽略#!的声明
shell 复制代码
/bin/sh test.sh

如果想要快速验证,可以直接在菜鸟编辑器上运行自己的脚本

变量

变量声明和删除

shell 中,变量的声明和删除的示例如下所示:(其他 # 在 shell 脚本中用于注释)

shell 复制代码
# 声明变量,注意变量名和等号之间不能有空格
your_name="小墙程序员"

# 引用变量,echo 函数的作用是向窗口输出文本,类似于 print
echo $your_name

# 声明只读变量
readonly your_name

# 删除变量,注意不能删除只读变量
unset variable_name

数据类型

在 shell 脚本中一般有四种变量类型,分别是字符串变量、数组变量、环境变量、特殊变量。

  • 字符串变量

在 shell 脚本中,字符串可以用单引号,也可以用双引号,也可以不用引号。代码示例如下:

shell 复制代码
# 不建议使用,特殊字符如空格等可能导致解析出问题
your_name=小墙程序员

# 单引号声明的字符串,类似于 kotlin 的三引号的字符串,里面不需要转义
# 同时也不能引用 $variable 变量
your_name='小墙程序员'

# 与'' 正相反,可以引用 $variable 变量,特殊字符需要转义
your_name="小墙程序员"

在 shell 脚本中,也支持获取字符串长度、字符串拼接等操作,代码示例如下:

shell 复制代码
your_name='12345678'

# 获取字符串长度,8
echo ${#your_name}

# 提取子字符串,结果为:2345
echo ${your_name:1:4}
  • 数组变量
shell 复制代码
# 定义数组,使用空格来分隔
# 数组下标由 0 开始
array_name=('value0' 'value1' 'value2' 'value3')

# 赋值数组单个位置
array_name[0]='value4'

# 获取数组指定位置的值
echo ${array_name[0]}

# 获取数组中所有的元素
echo ${array_name[@]}

# 获取数组的长度
echo ${#array_name[@]}
  • 环境变量

环境变量是由操作系统或用户设置的特殊变量,用于配置 Shell 的行为和影响其执行环境。我们可以通过 $ 符号直接获取。常见的环境变量有:

go 复制代码
`BASHPID`:Bash 进程的进程 ID。
`BASHOPTS`:当前 Shell 的参数,可以用`shopt`命令修改。
`EDITOR`:默认的文本编辑器。
`HOME`:用户的主目录。
`HOST`:当前主机的名称。
`LANG`:字符集以及语言编码,比如`zh_CN.UTF-8`。
`PATH`:由冒号分开的目录列表,当输入可执行程序名后,会搜索这个目录列表。
`PS1`:Shell 提示符。
`PS2`: 输入多行命令时,次要的 Shell 提示符。
`PWD`:当前工作目录。
`RANDOM`:返回一个0到32767之间的随机数。
`SHELL`:Shell 的名字。
`TERM`:终端类型名,即终端仿真器所用的协议。
`UID`:当前用户的 ID 编号。
`USER`:当前用户的用户名。
  • 特殊变量

    ruby 复制代码
      **`$?`** :用于获取上一个命令的退出码。若返回值为`0`,表明上一个命令执行成功;非零值则表示执行失败。例如,执行`ls doesnotexist`命令后,因文件不存在报错,此时`$?`的值为`1`,说明该命令执行失败。
      **`$$`** :代表当前 Shell 的进程 ID
      **`$_`** :存储上一个命令的最后一个参数。例如执行`grep dictionary xxx`后,`$ _`的值为`xxx`。
      **`$!`** :返回最近一个后台执行的异步命令的进程 ID
      **`$0`**:在命令行直接执行时表示当前 Shell 的名称,在脚本中执行时表示脚本名。**$1**、**$2** 等表示脚本的参数
      **`$-`**:体现当前 Shell 的启动参数。
      **`$#`**: 传递到脚本的参数个数
      **`$*`** :以一个单字符串显示所有向脚本传递的参数
      **`$@`** :与$*相同,不同点是在脚本运行时写了三个参数 1、2、3,则 " * " 等价于 "1 2 3"(传递了一个参数),而 "@" 等价于 "1" "2" "3"(传递了三个参数)

部分shell支持整数变量,可以使用declaretypeset 命令来声明整数变量

多行注释

在shell中支持多种格式的多行注释,代码示例如下:

shell 复制代码
# 使用 Here 文档来多行注释
:<<EOF
注释内容...
注释内容...
注释内容...
EOF

# : + 空格 + 单引号 表示多行注释
: '  
这是注释的部分。  
可以有多行内容。  
'

算术运算

shell 脚本不能直接进行算术运算。需要使用 (()) 语法或者 expr 命令

(()) 语法

代码示例如下:

shell 复制代码
# (()) 语法不会返回值,需要使用$来获取术运算的结果
echo $((2 + 2))

# 支持嵌套,** 表示指数
echo $(((5**2) * 3))

# 只能计算整数,否则会报错
echo $((1.5 + 1))

# $[...]是以前的语法,也可以做整数运算,不建议使用
echo $[2+2]

注意:如果在$((...))里面使用字符串,Bash 会认为那是一个变量名。如果不存在同名变量,Bash 就会将其作为空值,因此不会报错

除了算术运算外,(()) 语法还可以进行逻辑运算、赋值运算

shell 复制代码
# 逻辑运算,如果逻辑表达式为真,返回1,否则返回0
echo $((3 > 2))

# 赋值运算
foo=5
echo $((foo*=2))

expr 命令

shell 复制代码
foo=3
expr $foo + 2

expr命令支持算术运算,可以不使用((...))语法。需要注意 expr 命令也不支持非整数参数。

方法

shell 的函数定义格式如下所示,可以看到它的格式和我们使用 kotlin、js等编程语言类似,但是还是有很大的不同。

shell 复制代码
[ function ] funname [()]

{

    action;

    [return int;]

}
  • 不同点1:funname() 不能带任何参数

在shell函数声明中,不能带任何的参数。而是使用 <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 , 1, </math>1,2,...,$n 来获取参数。代码示例如下:

shell 复制代码
#!/bin/bash

funWithParam(){
    echo "第一个参数为 $1 !"
    echo "第二个参数为 $2 !"
    # 当 n >= 10 时,需要使用 ${n},否则会获取失败
    echo "第十个参数为 $10 !" 
    echo "第十个参数为 ${10} !" 
    echo "第十一个参数为 ${11} !"
    echo "参数总数有 $# 个!"
    echo "作为一个字符串输出所有参数 $* !"
}
# 注意:所有函数在使用前必须定义
funWithParam 1 2 3 4 5 6 7 8 9 34 73
  • 不同点2:return返回值,只能为[0, 255]之间的整数

代码示例如下:

shell 复制代码
# return 返回
funWithReturn(){  
    return 1
}
funWithReturn

# 不加return,将以最后一条命令运行结果作为返回值
demoFun(){  
    echo "no return"  
}
demoFun
  • 不同点3:使用 $? 来获取返回值

代码示例如下:

shell 复制代码
#!/bin/bash
funWithReturn(){
    return 5
}
funWithReturn
echo " $? "

逻辑判断与控制

if

  • if 语法
shell 复制代码
# if 语法
if condition
then
    command1 
    command2
    ...
    commandN 
fi

# 写成一行,需要分号分隔,适用于终端命令提示符
if condition; then command1; fi

代码示例如下:

shell 复制代码
if true
then
  echo 'hello world'
fi
  • if-else 语法
shell 复制代码
if condition
then
    command1 
    command2
    ...
    commandN
else
    command
fi
  • if else-if else 语法
shell 复制代码
if condition1
then
    command1
elif condition2 
then 
    command2
else
    commandN
fi
  • test 命令

if结构的判断条件,一般使用test命令,如下代码所示:

shell 复制代码
# 写法一
test expression
# 写法二
[ expression ]
# 写法三,支持正则判断
[[ expression ]]

代码示例如下:

shell 复制代码
# 写法一
if test -e test.txt # -e 表示检查文件是否存在,如果存在则返回 true
then
  echo "Found test.txt"
fi
# 写法二
if [ "$name" == "小墙程序员" ] 
then
  echo "current user is root"
fi
# 写法三
if [[ -e /tmp/foo.txt ]] ; then
  echo "Found test.txt"
fi

for 循环

shell 脚本的 for 循环的语法如下所示:

shell 复制代码
for var in item1 item2 ... itemN
do
    command1
    command2
    ...
    commandN
done

代码示例如下:

shell 复制代码
for loop in 1 2 3 4 5
do
    echo "The value is: $loop"
done

如果想要使用 for 表示无限循环,可以使用 for (( ; ; ))

while

shell 脚本的 while 循环的语法如下:

shell 复制代码
while condition
do
    command
done

代码示例如下:

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

until

shell 支持 until 循环,它和 while 正好相反。当条件语句为 true 是,until 循环退出。语法如下所示:

shell 复制代码
until condition
do
    command
done

代码示例如下:

shell 复制代码
#!/bin/bash

a=0

until [ ! $a -lt 10 ]
do
   echo $a
   a=`expr $a + 1`
done

case

在 shell 脚本中,使用 case 来代替其他语言中 switch 的作用。语法格式如下:

shell 复制代码
case 值 in
模式1)
    command1
    command2
    ...
    commandN
    ;;
模式2)
    command1
    command2
    ...
    commandN
    ;;
esac

代码示例如下:

shell 复制代码
echo '输入 1 到 4 之间的数字:'
echo '你输入的数字为:'
read aNum # 读取键盘的输入,并赋值给 aNum
case $aNum in
    1)  echo '你选择了 1'
    ;;
    2)  echo '你选择了 2'
    ;;
    3)  echo '你选择了 3'
    ;;
    4)  echo '你选择了 4'
    ;;
    *)  echo '你没有输入 1 到 4 之间的数字'
    ;;
esac

break 和 continue

如果你需要跳出循环,shell 脚本也支持 break 跳出所有的循环,以及 continue 跳出当前的循环。

输入输出

标准文件

一般情况下,每个 shell 命令运行时都会打开三个标准文件,分别是:

  • 标准输入文件(stdin):stdin的文件描述符为0,Unix程序默认从stdin读取数据。
  • 标准输出文件(stdout):stdout 的文件描述符为1,Unix程序默认向stdout输出数据。
  • 标准错误文件(stderr):stderr的文件描述符为2,Unix程序会向stderr流中写入错误信息。

重定向

一般来说,shell 脚本执行后的结果都是会显示在终端,如果你想要改变显示的位置,可以选择重定向命令。

  • command > file 将输出重定向到 file,默认是将标准输出重定向到指定file
  • command < file 将输入重定向到 file,默认是将标准输入重定向到指定 file
  • command >> file 将输出以追加的方式重定向到 file,默认是将标准输出重定向到指定file

如果你不想使用默认的方式,可以直接通过增加文件描述符的形式来设置,代码示例如下:

shell 复制代码
# 设置为标准错误输出
command 2>file

# 将 stdout 和 stderr 合并后重定向到 file
command >> file 2>&1

# 将 stdin 重定向到 file1,将 stdout 重定向到 file2
command < file1 >file2

如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null;/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃.

文件引用

如果我们需要引用其他的脚本文件,可以使用 . 或者 source 关键字。代码示例如下:

shell 复制代码
. filename   # 注意点号(.)和文件名中间有一空格

source filename

参考

相关推荐
是谢添啊11 小时前
Shell 脚本 + cron 定时备份 Docker MySQL
mysql·docker·shell·cron·定时备份
星如雨落2 天前
Linux Debian安装ClamAV和命令行扫描病毒方法
linux·shell
bkspiderx4 天前
Xshell 和 Xftp 更新提示问题的解决方法及分析
bash·shell·xshell·xftp·破解版本更新
码农明明4 天前
强大的壳-Shell Script
linux·shell
星如雨落5 天前
Linux shell脚本对常见图片格式批量转换为PDF文件
linux·shell
qq_433618447 天前
shell 编程(二)
开发语言·bash·shell
酥心糖小可爱9 天前
shell脚本案例
shell·脚本
桃酥40313 天前
GCC实用干货
linux·shell·gcc
月光技术杂谈17 天前
5G模组AT命令脚本-命令发送及回显读取
linux·5g·shell·5g模组·5g终端·at命令