乐趣区

关于前端:写给前端的-shell-脚本编程详解

我的博客原文链接:https://chenfangxu.lap.360.cn/assistive-tools/shell/script.html


Shell 脚本(shell script),是一种为 Shell 编写的脚本程序,个别文件后缀为 .sh

脚本解释器

#! 是一个约定的标记,它通知零碎这个脚本须要什么解释器来运行,即应用哪一种 shell。#!被称为 shebang(也称为 Hashbang), 例如应用 bash:#! /bin/bash

新建一个 test.sh 的文件,内容如下:

#!/bin/bash

echo "Hello World!"

运行 Shell 脚本

第一种形式:作为可执行程序

1、以后 test.sh 是没有可执行权限的,首先使脚本文件具备执行权限。

# 使脚本文件具备执行权限
chmod +x ./test.sh

2、执行脚本

# 执行脚本,需注意要加目录的标识
./test.sh

# 也能够用 source 来执行脚本,跟下面的写法是等价的,然而不须要脚本具备执行权限
source ./test.sh

留神:肯定要写成 ./test/sh , 而不是 test.sh。运行其余二进制的程序也是一样,间接写 test.sh,Linux 零碎会去 PATH 中寻找有没有叫 test.sh 的,而只有 /bin, /sbin, /usr/bin, /usr/sbin 等在 PATH 中。你的当前目录通常不在 PATH 中,所以写成 test.sh 是找不到命令的,要用./test.sh 通知零碎,就在当前目录找。

通过这种形式运行 bash 脚本,第一行肯定要写对,好让零碎(Shell 程序)查找到正确的解释器。如果是应用规范默认的 shell,能够省去第一行。

作为解释器参数

间接运行解释器,其参数就是 Shell 脚本的文件名。

/bin/bash test.sh

这种形式运行的脚本,不须要在第一行指定解释器信息,写了也没用。

语法

1、正文

  • 单行正文:以 # 结尾,到行尾完结。
  • 多行正文:以 :<<EOF 结尾,到 EOF 完结
# 这是单行正文

:<<EOF
这是多行正文
这是多行正文
EOF

如果有段代码要频繁的正文和勾销正文,能够用花括号括起来,定义成一个函数,没有中央调用这个函数,这块代码就不会执行,达到了和正文一样的成果。

2、变量

变量类型

  • 局部变量:局部变量是仅在某个脚本外部无效的变量。它们不能被其余的程序和脚本拜访。
  • 环境变量:环境变量是从父过程中继承而来的变量,对以后 Shell 会话内所有的程序和脚本都可见。创立它们跟创立局部变量相似,但应用的是 export 关键字,shell 脚本也能够定义环境变量。
  • shell 变量(零碎变量):shell 变量是由 shell 程序设置的非凡变量。shell 变量中有一部分是环境变量,有一部分是局部变量,这些变量保障了 shell 的失常运行。

变量语法

1、申明变量

能够应用等号操作符为变量赋值:varName=value,varName 是变量名,value 是赋值给变量的值。

变量名的命名规定:

  • 首字母必须为字母(a-z,A-Z),剩下的局部只能应用英文字母,数字下划线
  • 两头不能有空格,能够应用下划线,如果有空格,必须应用单引号或双引号
  • 不能应用标点符号
  • 不能应用 shell 关键字
#!/bin/bash

fruit=apple
count=5

留神:varName=value的等号两边没有空格,变量值如果有空格,须要用引号包住。

2、拜访变量

拜访变量的语法模式为:${varName}$varName,变量名里面的花括号是可选的,加不加都行,加花括号是为了帮忙解释器辨认变量的边界(举荐加花括号)。

#!/bin/bash

fruit=apple
count=5
echo "We have $count ${fruit}s"
#输入:We have 5 apples

因为 Shell 应用空白字符来分隔单词,所以下面的例子中须要加上花括号来通知 Shell 这里的变量名是 fruit,而不是 fruits

留神:应用单引号时,变量不会被扩大 (expand),仍按照原样显示。这意味着 echo '$var' 会显示 \$var。应用双引号时,如果 \$var 曾经定义过,那么 echo "$var"会显示出该变量的值,如果没有定义过,则什么都不显示。

3、只读变量

应用 readonly 命令能够将变量定义为只读变量,只读变量的值不能被扭转

#!/bin/bash

fruit=apple
echo $fruit
readonly fruit
#fruit=banana  #如果放开正文,执行时会报错

4、删除变量

应用 unset 命令能够删除变量,变量被删除后不能再次应用。unset 命令不能删除只读变量

#!/bin/bash

fruit=apple
echo $fruit
#输入:apple

unset fruit
echo $fruit
#输入:(空)

Shell 非凡变量(零碎变量)

下面讲过变量名的命名规定,然而还有一些蕴含其余字符的变量有非凡含意,这样的变量被称为非凡变量。

变量 含意
$0 以后脚本的文件名
$n 传递给脚本或函数的参数。n 是一个数字,示意第几个参数。例如,第一个参数是 \$1
$# 传递给脚本或函数的参数个数
$* 传递给脚本或函数的所有参数
$@ 传递给脚本或函数的所有参数,被双引号(””)蕴含时,与 \$\* 稍有不同
$FUNCNAME 函数名称(仅在函数内值)
$? 上个命令的退出状态,或函数的返值
$- 显示 shell 应用的以后选项(flag),前面扩大中检测是否为交互模式时会用到
$$ 以后 Shell 过程 ID。对于 Shell 脚本,就是这些脚本所在的过程 ID
$! 最初一个后盾运行的过程 ID 号

命令行参数:运行脚本时传递给脚本的参数成为命令行参数,命令行参数用 $n 示意。

#!/bin/bash

# ./test.sh

echo "文件名:$0"
echo "第一个命令行参数:$1"
echo "第二个命令行参数:$2"
echo "传入的全副参数:$@"
echo "传入的全副参数:$*"
echo "全副参数的数量:$#"

执行命令:./test.sh Linux Shell,后果为:

文件名:./test.sh
第一个命令行参数:Linux
第二个命令行参数:Shell
传入的全副参数:Linux Shell
传入的全副参数:Linux Shell
全副参数的数量:2

$? 能够获取上一个命令的退出状态。所谓退出状态,就是上一个命令执行后的返回后果。退出状态是一个数字,个别状况下,大部分命令执行胜利会返回 0,失败会返回 1。$? 也能够示意函数的返回值。

3、字符串

字符串引号

shell 字符串能够应用单引号 '',也能够应用双引号"" ,也能够不必引号。

  • 单引号:不辨认变量,单引号两头不能呈现独自的单引号(应用转义字符本义也不行),能够成对呈现实现字符串拼接。
name='world'

before='hello,'${name}'!' #应用单引号拼接字符串
after='hello,${name}!'  #单引号中变量不解析

echo ${before}_${after}
# 输入:hello,world!_hello,${name}!
  • 双引号:能够辨认变量,双引号中能够呈现用转义字符本义的双引号
name="\"shell\""  #双引号内容许呈现被本义的双引号

before="hello,"${name}"!" #应用双引号拼接字符串
after="hello,${name}!" #双引号中变量会解析

echo ${before}_${after}
# 输入:hello,"shell"!_hello,"shell"!

设置一个字符串变量,上面的都是对这个变量的操作

file=/dir1/dir2/dir3/my.file.txt

${#var}:取得变量值的长度

echo ${#file}
# 输入:27

${varx}:通过索引地位截取子字符串

echo ${file:0:5} #截取最左侧的 5 个字符
# 输入:/dir1

echo ${file:5:5} #从第 6 个字符开始,截取 5 个字符
# 输入:/dir2

${var#}、${var##}:删除字符串左侧的值

echo ${file#*/} #删除第一个 / 及其左侧的字符串
# 输入:dir1/dir2/dir3/my.file.txt

echo ${file##*/} #删除最初一个 / 及其左侧的字符串
# 输入:my.file.txt

echo ${file#*.} #删除第一个 . 及其左侧的字符串
# 输入:file.txt

echo ${file##*.} #删除最初一个 . 及其左侧的字符串
# 输入:txt

${var%}、${var%%}:删除字符串右侧的值

echo ${file%/*} #删除最初一个 / 及其右侧的字符串
# 输入:/dir1/dir2/dir3

echo ${file%%/*} #删除第一个 / 及其右侧的字符串
# 输入:(空值)

echo ${file%.*} #删除最初一个 . 及其右侧的字符串
# 输入:/dir1/dir2/dir3/my.file

echo ${file%%.*} #删除第一个 . 及其右侧的字符串
#输入:/dir1/dir2/dir3/my

${var:-word}:如果变量 var 为空、没有定义或已被删除(unset),那么返回 word,但不扭转 var 的值。

echo ${var:-"var is not set"}
#输入:var is not set

echo "var is ${var}"
#此时 var 还是没有定义,所以输入:var is

${var:=word}:如果变量 var 为空、没有定义或者已被删除,那么返回 word,并将 var 的值设置为 word。

echo ${var:="var is not set"}
#输入:var is not set

echo "var is ${var}"
#此时 var 曾经定义为 var is not set 了,所以输入:var is var is not set

${var:?message}:如果变量 var 为空、没有定义或者已被删除,那么将音讯 message 送到规范谬误输入。

能够用来检测变量 var 是否能够被失常赋值。若此替换呈现在 shell 脚本中,那么脚本将进行运行。

${var:+word}:如果变量 var 被定义,那么返回 word,但不扭转 var 的值。

数组

数组是能够存储多个值的变量,这些值能够独自援用,也能够作为整个数组来援用。数组的下标从 0 开始,下标能够是整数或算数表达式,其值应该大于等于 0。

创立数组

numbers=(one two three four five)

#创立数组时指明下标
colors=([1]=red [0]=yello [2]=blue)

拜访数组

拜访单个元素

echo ${numbers[2]}
#输入:three

拜访数组的所有元素

echo ${colors[*]}
#输入:yello red blue

echo ${colors[@]}
#输入:yello red blue

${colors[*]}${colors[@]} 有些轻微的差异,在将数组中的每个元素独自一行输入的时候,有没有被引号包住会有不同的差异,在引号内,${colors[@]}将数组中的每个元素扩大为一个独自的参数,数组元素中的空格得以保留。

拜访数组局部元素

# :0:2 去除数组中从 0 开始,长度为 2 的数组元素
echo ${colors[@]:0:2}
#输入:yello red

数组的长度

echo ${#colors[@]}
#输入:3

数组中增加元素

colors=(white "${colors[@]}" green black)

echo ${colors[@]}
#输入:white yello red blue green black
echo ${#colors[@]}
#输入:6

数组中删除元素

unset colors[2]

echo ${colors[@]}
#输入:white yello blue green black
echo ${#colors[@]}
#输入:5

残缺的代码示例:

#!/bin/bash


numbers=(one two three four five)

colors=([1]=red [0]=yello [2]=blue)

echo ${numbers[2]}

echo ${colors[*]}

echo ${colors[@]}

echo ${colors[@]:0:2}

echo ${#colors[@]}

colors=(white "${colors[@]}" green black)

echo ${colors[@]}

echo ${#colors[@]}

unset colors[2]

echo ${colors[@]}

echo ${#colors[@]}

运算符

Shell 中有很多运算符,包含算数运算符、关系运算符、布尔运算符、字符串运算符和文件测试符。

算数运算符

原生 bash 不反对简略的数学运算,较为罕用的是借助 expr 来实现数学运算。

算数运算符列表,变量 a 是 10 变量 b 是 50

运算符 阐明 举例
+ 加法 expr ${a} + ${b} 后果为 60
减法 expr ${b} - ${a} 后果为 40
\* 乘法 expr ${a} \\* ${b} 后果为 500
/ 除法 expr ${b} / ${a} 后果为 5
% 取余 expr ${b} % ${a} 后果为 0
= 赋值 a=$b 就是失常的变量赋值

示例代码如下:

#!/bin/bash

a=10
b=50

value=`expr ${a} + ${b}`
echo "a + b = ${value}"

value=`expr ${b} - ${a}`
echo "b - a = ${value}"

value=`expr ${a} \* ${b}`
echo "a * b = ${value}"

value=`expr ${b} / ${a}`
echo "b / a = ${value}"

value=`expr ${b} % ${a}`
echo "b % a = ${value}"

#输入
#a + b = 60
#b - a = 40
#a * b = 500
#b / a = 5
#b % a = 0

留神:

  1. 表达式和运算符之间要有空格,例如 1+1 是谬误的,必须写成1 + 1
  2. 残缺的表达式要用反引号 ` 包住
  3. 条件表达式要放在方括号之间,并且要有空格,例如 [${a}==${b}]是谬误的,必须写成 [${a} == ${b} ]

条件运算符(关系运算符)

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

关系运算符列表,变量 a 是 10 变量 b 是 50

运算符 阐明 举例
-eq 检测两个数是否相等,相等返回 true [${a} -eq ${b} ] 返回 false
-ne 检测两个数是否不相等,不相等返回 true [${a} -ne ${b} ] 返回 true
-gt 检测右边的数是否大于左边的数,如果是,返回 true [${a} -gt ${b} ] 返回 false
> 跟 -gt 一样,不过因为兼容性问题,可能要在 [[]] 表达式中应用 [[${a} > ${b} ]] 返回 false
-lt 检测右边的数是否小于左边的数,如果是,返回 true [${a} -lt ${b} ] 返回 true
< 跟 -lt 一样,不过因为兼容性问题,可能要在 [[]] 表达式中应用 [[${a} < ${b} ]] 返回 true
-ge 检测右边的数是否大于等于左边的数,如果是,返回 true [${a} -ge ${b} ] 返回 false
-le 检测右边的数是否小于等于左边的数,如果是,返回 true [${a} -le ${b} ] 返回 true

实例代码如下:

!/bin/bash

a=10
b=50

if [${a} -eq ${b} ]; then
  echo "${a} -eq ${b} : a 等于 b"
else
  echo "${a} -eq ${b} : a 不等于 b"
fi
#输入:10 -eq 50 : a 不等于 b

if [${a} -ne ${b} ]; then
  echo "${a} -ne ${b} : a 不等于 b"
else
  echo "${a} -ne ${b} : a 等于 b"
fi
#输入:10 -ne 50 : a 不等于 b

if [${a} -gt ${b} ]; then
  echo "${a} -gt ${b} : a 大于 b"
else
  echo "${a} -gt ${b} : a 小于 b"
fi
#输入:10 -gt 50 : a 小于 b
if [[${a} > ${b} ]]; then
  echo "${a} > ${b} : a 大于 b"
else
  echo "${a} > ${b} : a 小于 b"
fi
#输入:10 > 50 : a 小于 b

if [${a} -lt ${b} ]; then
  echo "${a} -lt ${b} : a 小于 b"
else
  echo "${a} -lt ${b} : a 大于 b"
fi
#输入:10 -lt 50 : a 小于 b
if [[${a} < ${b} ]]; then
  echo "${a} < ${b} : a 小于 b"
else
  echo "${a} < ${b} : a 大于 b"
fi
#输入:10 < 50 : a 小于 b

if [${a} -ge ${b} ]; then
  echo "${a} -ge ${b} : a 大于等于 b"
else
  echo "${a} -ge ${b} : a 小于等于 b"
fi
#输入:10 -ge 50 : a 小于等于 b

if [${a} -le ${b} ]; then
  echo "${a} -le ${b} : a 小于等于 b"
else
  echo "${a} -le ${b} : a 大于等于 b"
fi
#输入:10 -le 50 : a 小于等于 b

条件运算符(布尔运算符、逻辑运算符、字符串运算符)

条件运算符列表,变量 a 是 10,变量 b 是 50,变量 x 是 “abc”,变量 y 是 “efg”

运算符 阐明 举例
! 非运算 [! false] 返回 true
-o 或运算 [${a} -eq 10 -o ${b} -eq 100 ] 返回 true
-a 与运算 [${a} -eq 10 -a ${b} -eq 50 ] 返回 true
&& 跟 -a 相似,逻辑的 AND,不过须要应用 [[]] 表达式 [[${a} -eq 10 && ${b} -eq 50 ]] 返回 true
= 检测两个数字或字符串是否相等,相等返回 true [${a} = ${b} ] 返回 false
!= 检测两个数字或字符串是否相等,不相等返回 true [${a} != ${b} ]返回 true
== 相等。比拟两个数字或字符串,如果相等返回 true(不举荐应用,有兼容性问题) [${a} == ${b} ] 返回 false
-z 检测字符串长度是否为 0,为 0 返回 true [-z ${x} ] 返回 false
-n 检测字符串长度是否为 0,不为 0 返回 true [-n ${x} ] 返回 true
var 检测变量是否存在或不为空,存在或不为空返回 true [$s] 返回 false

代码示例如下:

#!/bin/bash

a=10
b=50
x="abc"
y="edf"

#单 []
if [${a} -eq 10 -a ${b} -eq 50 ]; then
  echo "${a} -eq 10 -a ${b} -eq 50 : 返回 true"
else
  echo "${a} -eq 10 -a ${b} -eq 50 : 返回 false"
fi
#输入:10 -eq 10 -a 50 -eq 50 : 返回 true

#双 []
if [[${a} -eq 10 && ${b} -eq 50 ]]; then
  echo "${a} -eq 10 && ${b} -eq 50 : 返回 true"
else
  echo "${a} -eq 10 && ${b} -eq 50 : 返回 false"
fi
#输入:10 -eq 10 && 50 -eq 50 : 返回 true

if [${a} = ${b} ]
then
  echo "a 和 b 相等"
fi

if [${a} != ${b} ]
then
  echo "a 和 b 不相等"
fi
#a 和 b 不相等

if [-z ${x} ]; then
  echo "-z ${x}:字符串长度为 0"
else
  echo "-z ${x}:字符串长度不为 0"
fi
#输入:-z abc:字符串长度不为 0

if [-n ${y} ]; then
  echo "-z ${y}:字符串长度不为 0"
else
  echo "-z ${y}:字符串长度为 0"
fi
#输入:-z edf:字符串长度不为 0

if [$x];then
  echo "${x}:不是空字符串"
else
  echo "${x}:是空字符串"
fi
#输入:abc:不是空字符串

if [$s];then
  echo '${s}:存在'
else
  echo '${s}:不存在'
fi
#输入:${s}:不存在

文件目录判断运算符

文件目录判断运算符列表

运算符 阐明
-f filename 判断文件是否存在,当 filename 存在且是正规文件时(既不是目录,也不是设施文件)返回 true
-d pathname 判断目录是否存在,当 pathname 存在且是目录时返回 true
-e pathname 判断【某个货色】是否存在,当 pathname 指定的文件或目录存在时返回 true
-a pathname 同上,曾经过期,而且应用的时候还有另外一个与的逻辑,容易混同
-s filename 判断是否是一个非空文件,当 filename 存在并且文件大小大于 0 时返回 true
-r pathname 判断是否可读,当 pathname 指定的文件或目录存在并且可读时返回 true
-x pathname 判断是否可执行,当 pathname 指定的文件或目录存在并且可执行时返回 true
-w pathname 判断是否可写,当 pathname 指定的文件或目录存在并且可写时返回 true
-b filename 判断是否是一个块文件,当 filename 存在且是块文件时返回 true
-c filename 判断是否是一个字符文件,当 filename 存在且是字符文件时返回 true
-L filename 判断是否是一个符号链接,当 filename 存在且是符号链接时返回 true
-u filename 判断文件是否设置 SUID 位,SUID 是 Set User ID
-g filename 判断文件是否设置 SGID 位,SGID 是 Set Group ID

示例代码如下:

#!/bin/bash

file="/etc/hosts"

if [-f ${file} ]; then
  echo "${file}:是一个一般文件"
else
  echo "${file}:不是一个一般文件"
fi
#输入:/etc/hosts:是一个一般文件

if [-d ${file} ]; then
  echo "${file}:是个目录"
else
  echo "${file}:不是目录"
fi
#输入:/etc/hosts:不是目录

if [-e ${file} ]; then
  echo "${file}:文件存在"
else
  echo "${file}:文件不存在"
fi
#输入:/etc/hosts:文件存在

if [-s ${file} ]; then
  echo "${file}:文件不为空"
else
  echo "${file}:文件为空"
fi
#输入:/etc/hosts:文件不为空

if [-r ${file} ]; then
  echo "${file}:文件可读"
else
  echo "${file}:文件不可读"
fi
#输入:/etc/hosts:文件可读

条件语句

在条件语句中,由 [][[]] 包起来的表达式被称为 检测命令 基元

if…fi 语句

语法:if [expression]
then
  expression 是 true,这里会被执行
fi
#!/bin/bash

if [1 = 1]
then
  echo "相等"
fi
#输入:相等

#也能够写成一行
if ["a" = "a"]; then echo "相等"; fi
#输入:相等

if…else 常常跟 test 命令联合应用,test命令用于查看某个条件是否成立,与方括号 [] 相似(它们两个在 /usr/bin 中是用软连贯指向的)。

#!/bin/bash

a=10
b=50

if test ${a} -eq ${b}
then
  echo "a 等于 b"
else
  echo "a 不等于 b"
fi
#输入:a 不等于 b

if…else…fi

语法:if [expression]
then
  expression 是 true,这里会被执行
else
  expression 是 false , 这里会被执行
fi
#!/bin/bash

if [1 = 2]
then
  echo "相等"
else
  echo "不相等"
fi
#输入:不相等

if…elif…fi

语法:if [expression1]
then
  expression 是 true,这里会被执行
elif [expression2]
then
  expression1 是 true,这里会被执行
else
  下面的 expression 都是 false , 这里会被执行
fi
#!/bin/bash

a=10
b=50

if [${a} -eq ${b} ]
then
  echo "a 等于 b"
elif [${a} -gt ${b} ]
then
  echo "a 大于 b"
else
  echo "a 小于 b"
fi
#输入:a 小于 b

case…esac

case…esac 与其余语言中的 switch…case 相似,是一种多分支抉择构造。

case 语句匹配一个值或一个模式,如果匹配胜利,执行想匹配的命令。实用于须要面对很多状况,别离要采取不同的措施的状况。

case 值 in
模式 1)
  command1
  command2
  command3
  ;;
模式 2)
  command1
  command2
  command3
  ;;
*)
  command1
  command2
  command3
  ;;
esac
#!/bin/bash

echo "输出 1 - 4 的一个数字"
echo "你输出的数字是:"

read number

case $number in
  1)
  echo "你输出了 1"
  ;;
  2)
  echo "你输出了 2"
  ;;
  3)
  echo "你输出了 3"
  ;;
  4)
  echo "你输出了 4"
  ;;
  *)
  echo "你输出的不是 1 - 4 的数字"
  ;;
esac

#运行后能够本人输出数字体验

留神:能够在 ) 前用 | 宰割多个模式。

循环语句

bash 中有四种循环:for , while , until , select

for 循环

语法:for 变量 in 列表
do
  command1
  command2
  ...
  commandN
done

语法中的列表是一组值(数字、字符串)组成的序列,每个值通过空格分隔。这些值还能够是通配符或大括号扩大,例如 *.sh{1..5}

#!/bin/bash

for i in 1 2 3 4 5
do
  echo $i
done

# 写在一行
for i in {1..5}; do echo $i ; done

while 循环

while 循环会一直的检测一个条件,只有这个条件返回 true,就执行一段命令。被检测的条件跟 if 中的一样。while 也可用于从输出文件中读取数据。

语法:while [[condition]]
do
  如果 condition 是 true,这里的命令会执行
done
#!/bin/bash

x=0
y=10

while [${x} -lt 5 ]
do
  echo $x
  x=`expr ${x} + 1`
done

# do 也跟条件写在一行,后面须要加分号 ;
while [${y} -gt 5 ]; do echo $y; y=`expr ${y} - 1`; done

until 循环

until 循环是检测一个条件,只有条件是 false 就会始终执行循环,直到条件条件为 true 时进行。它跟 while 正好相同。

#!/bin/bash

x=0

until [${x} -eq 5 ]; do
  echo ${x}
  x=`expr ${x} + 1`
done

select 循环

select 循环的语法跟 for 循环基本一致。它帮忙咱们组织一个用户菜单。

语法:select 变量 in 列表
do
  执行相应的命令
done

select 会打印列表的值以及他们的序列号到屏幕上,之后会提醒用户抉择,用户通常看到的提醒是 \$?,用户输出相应的信号,抉择的后果会被保留到变量中。

#!/bin/bash

#PS3——shell 脚本中应用 select 时的提示符
PS3="抉择你要装置的包管理工具,输出序号:"

select ITEM in bower npm gem pip
do
  echo "输出的包名称是:\c" && read PACKAGE
  case ${ITEM} in
    bower) echo "模仿 bower install ${PACKAGE}" ;;
    npm) echo "模仿 npm install ${PACKAGE}" ;;
    gem) echo "模仿 gem install ${PACKAGE}" ;;
    pip) echo "模仿 pip install ${PACKAGE}" ;;
    *) echo "包管理工具抉择谬误" ;;
  esac
  break #跳出循环
done

break 命令

break 命令容许跳出所有循环(终止执行前面的所有循环)。在嵌套循环中 break 命令前面还能够跟一个整数,示意跳出几层循环。

#!/bin/bash

# 当 x 等于 2,并且 y 等于 0,就跳出循环
for x in 1 2 3
do
  for y in 0 5
  do
   if [${x} -eq 2 -a ${y} -eq 0 ]
   then
     echo "x 等于 ${x},y 等于 ${y}"
     break 2
   else
    echo "${x} ${y}"
   fi
  done
done

continue 命令

continue 命令跟 break 命令相似,只有一点差异,它不会跳出所有循环,仅仅跳出以后循环。同样,continue 前面也能够跟一个数字,示意跳出第几层循环。

#!/bin/bash

# 当 x 等于 2,并且 y 等于 0,就跳出循环
for x in 1 2 3
do
  for y in 0 5
  do
   if [${x} -eq 2 -a ${y} -eq 0 ]
   then
     continue 2
   else
    echo "${x} ${y}"
   fi
  done
done

函数

  • shell 函数必须先定义后应用,调用函数仅应用其函数名即可。
  • 函数定义时,function关键字可有可无
  • 函数返回值:能够显式的应用 return 语句,返回值类型只能为整数(0-255)。如果不加 return 语句,会默认将最初一条命令运行后果作为返回值。
  • 函数返回值在调用该函数后,通过 $? 取得。
语法:中括号内示意可选

[function] function_name () {
  在这里执行命令
  [return value]
}
#!/bin/bash

hello () {
  echo "hello"
  world #函数嵌套
}

world () {echo "world"}

hello

参数

地位参数是在调用一个函数并传给它参数时创立的变量,见上文 Shell 非凡变量。

#!/bin/bash

funWithParam () {
  echo "第 1 个参数:$1"
  echo "第 2 个参数:$2"
  echo "第 3 个参数:$3"
  echo "谬误的获取第 10 个参数:$10"
  # $10 不能获取第 10 个参数,须要用 ${10},当 n>=10 时,须要应用 ${n} 获取参数。(其中有兼容性,某些 Shell 解释器两种都能获取到)
  echo "正确的获取第 10 个参数:${10}"
  echo "获取第 11 个参数:${11}"
  echo "获取传参的个数:$#"
  echo "获取所有的传参:$*"
  echo "以后函数的名称是:$FUNCNAME"
}

funWithParam 1 2 3 4 5 6 7 8 9 34 73

输入输出重定向

Unix 命令默认从规范输出设施(stdin)获取输出,将后果输入到规范输出设备(stdout)显示。个别状况下,规范输出设施就是键盘,规范输出设备就是显示器。

输入输出流

shell 接管输出,并以字符序列或字符流的模式产生输入。这些流能被重定向到文件或另一个流中。

个别状况下,每个 Unix/Linux 命令都会关上三个文件:规范输出文件、规范输入文件、规范谬误文件,三个文件描述符:

代码 描述符 形容
0 stdin 规范输出
1 stdout 规范输入
2 stderr 规范谬误输入

重定向

重定向让咱们能够管制一个命令的输出来自哪里,输入后果到什么中央。

输入重定向:命令的输入不仅能够是显示器,还能够很容的本义到文件,这被称为输入重定向。

语法:command > file  此语法会覆盖文件内容

command >> file 如果不心愿文件被笼罩,能够应用 >> 追加到文件开端

输出重定向:使 Unix 命令也能够从文件获取输出,这样原本要从键盘获取输出的命令会转移到文件读取内容。

语法:command < file

有一个文件是 test.sh,用两种形式输入文件的行数

wc -l ./test.sh
#输入:14 ./test.sh

wc -l < ./test.sh
#输入:14  没有文件名

第一个例子会输入文件名,第二个不会,因为它仅仅晓得从规范输出读取的内容。

以下操作符在控制流的重定向时会被用到:

操作符 形容
> 重定向输入
>> 将输入已追加的形式重定向
>& 将两个输入文件合并
<& 将两个输出文件合并
< 重定向输出
<< Here 文档语法(见下文扩大),将开始标记 tag 和完结标记 tag 之间的内容作为输出
<<< Here 字符串

如果心愿 stderr 重定向到 file,能够这样写:

command 2 > file

如果心愿将 stdout 和 stderr 合并后重定向的 file,能够这样写:

#&[n] 代表是曾经存在的文件描述符,&1 代表输入 &2 代表谬误输入 &- 代表敞开与它绑定的描述符
command > file 2 >&1

如果心愿 stdin 和 stdout 都重定向,能够这样写:

command < file1 > file2

#例如:cat < test.sh  > catfile

#提一下 << 这个间断两个小符号,他代表的是『完结的输出字符』的意思。这样当空行输出 eof 字符,输出主动完结,不必 ctrl+D
cat <<eof >catfile

如果心愿执行某个命令,但又不心愿在屏幕上显示输入后果,那么能够将输入重定向到 /dev/null。

/dev/null 是一个非凡的文件,写入到它的内容都会被抛弃,如果尝试从该文件读取内容,那么什么也读不到。然而 /dev/null 文件十分有用,将命令的输入重定向到它,会起到 ” 禁止输入 ” 的成果。

#test1.sh 没有的状况下,将谬误输入信息敞开掉
ls test.sh test1.sh 2>/dev/null

ls test.sh test1.sh 2>&-

#敞开所有输入
ls test.sh test1.sh  1>&- 2>&-
ls test.sh test1.sh  2>/dev/null 1>/dev/null

#将谬误输入 2 绑定给 正确输入 1,而后将 正确输入 发送给 /dev/null 设施  这种罕用
ls test.sh test1.sh >/dev/null 2>&1
#& 代表规范输入,谬误输入 将所有规范输入与谬误输入 输出到 /dev/null 文件
ls test.sh test1.sh &>/dev/null

加载内部脚本

像其余语言一样,Shell 也能够加载内部脚本,将内部脚本的内容合并到以后脚本。shell 中加载内部脚本有两种写法。

第一种:. filename

第二种:source filename

两种形式成果雷同,简略起见,个别应用点号(.),然而!留神点号(.)和文件名两头有一个空格

#!/bin/bash

. ./pre_test.sh

echo $a
# 输入:100

Debug

全局 Debug

shell 提供了用于 debug 脚本的工具。如果想采纳 debug 模式运行某脚本,能够在其 shebang 中应用一个非凡的选项。(有些 shell 不反对)

#!/bin/bash [options]

或者在执行 Bash 脚本的时候,从命令行传入这些参数

bash -euxo pipefail test.sh

部分 Debug

有时咱们只须要 debug 脚本的一部分。这种状况下,应用 set 命令会很不便。这个命令能够启用或禁用选项。应用 - 启用选项,应用 + 禁用选项。

1、用来在运行后果之前,先输入执行的那一行命令

set -x
#或者
set -o xtrace

2、执行脚本时,如果遇到不存在的变量会报错,并进行执行。(默认是疏忽报错的)

set  -u
#或者
set  -o nounset

顺便说一下,如果命令行下不带任何参数,间接运行set,会显示所有的环境变量和 Shell 函数。

3、执行脚本时,产生谬误就终止执行。(默认是继续执行的)

set  -e
#或者
set -o errexit

#能够用上面是办法
command || exit 1
#或者
command1 && command2

set -e 依据返回值来判断,一个命令是否运行失败。然而,某些命令的非零返回值可能不示意失败,或者开发者心愿在命令失败的状况下,脚本继续执行上来。这时能够临时敞开 set -e,该命令执行完结后,再从新关上 set -e。

set +e
command1
command2
set -e

#也能够用上面的办法
command || true

4、管道命令执行失败,脚本终止执行

管道命令就是多个子命令通过管道运算符(|)组合成为一个大的命令。Bash 会把最初一个子命令的返回值,作为整个命令的返回值。最初一个子命令不失败,管道命令就总是会执行胜利的,因而 set -e 会生效,前面的命令会继续执行。

set -o pipefail 用来解决这个状况,只有一个子命令失败,整个管道命令就会失败,脚本就会终止执行。

set -eo pipefail

下面的命令能够放在一起应用:

set -euxo pipefail
#或者
set -eux
set -o pipefail

扩大

脚本解释器在环境变量中指定

除了比拟常见的用门路指定脚本解释器的形式,还有一种是指定环境变量中的脚本解释器。

指定脚本解释器的门路
#!/bin/bash`

指定环境变量中的脚本解释器
#!/usr/bin/env bash

这样做的益处是,零碎会主动在 PATH 环境变量中查找指定的程序(如例子中的 bash)。因为程序的门路是不确定的,比方装置完新版本的 bash 后,咱们有可能会把这个新的门路增加到 PATH 中,来“暗藏”老版本的 bash。所以操作系统的 PATH 变量有可能被配置为指向程序的另一个版本,如果还是间接用 #!/bin/bash,那么零碎还是会抉择老版本的 bash 来执行脚本,如果用#!/usr/bin/env bash,就会应用新版本了。

环境变量

所有的程序,包含 Shell 启动的程序运行时都能够拜访的变量就是环境变量。在 shell 脚本中应用 export 能够定义环境变量,然而只在以后运行的 shell 过程中无效,完结过程就没了。如果想长久化,须要将环境变量定义在一些列配置文件中。

配置文件的加载程序和 shell 过程是否运行在 Interactive 和 Login 模式无关。

交互和非交互模式(Interactive & Non-Interactive)

  • Interactive 模式:通常是指读写数据都是从用户的命令行终端(terminal),用户输出命令,并在回车后立刻执行的 shell。
  • Non-Interactive 模式:通常是指执行一个 shell 脚本,或 bash -c 执行命令

检测以后 shell 运行的环境是不是 Interactive 模式

[[$- == *i*]] && echo "Interactive" || echo "Non-interactive"

登录和非登录模式(Login & Non-Login)

  • Login 模式:利用在终端登陆时,ssh 连贯时,su –login <username> 切换用户时,指的是用户胜利登录后开启的 Shell 过程,此时会读取 /etc/passwd 下用户所属的 shell 执行。
  • Non-Login 模式:利用在间接运行 bash 时,su <username> 切换用户时(后面没有加 –login)。指的是非登录用户状态下开启的 shell 过程。

检测以后 shell 运行的环境是不是 Login 模式

shopt -q login_shell && echo "Login shell" || echo "Not login shell"

#如果是 zsh,没有 shopt 命令
[[-o login]] && echo "Login shell" || echo "Not login shell"

进入 bash 交互模式时也能够用 –login 参数来决定是否是登录模式:

$> bash
$> shopt -q login_shell && echo "Login shell" || echo "Not login shell"
Not login shell
$> exit
$> bash --login
$> shopt -q login_shell && echo "Login shell" || echo "Not login shell"
Login shell
$> exit

Login 模式模式下能够用 logout 和 exit 退出,Non-Login 模式下只能用 exit 退出。

配置文件(启动文件)加载程序

bash 反对的配置文件有 /etc/profile、~/.bash.rc 等。

如上图加载程序所示

  • Interactive&Login 模式:/etc/profile —>(~/.bash_profile, ~/.bash_login, ~/.profile)其中之一 —>~/.bash_loginout(退出 shell 时调用)
  • Interactive&Non-Login 模式:/etc/bash.bashrc —>~/.bashrc
  • Non-Interactive 模式:通常就是执行脚本(script)的时候,此时配置项是从环境变量中读取和执行的,也就是 env 或者 printenv 命令输入的配置项。

当初的零碎个别都没有 ~/.bash_profile 文件了,只保留 ~/.bashrc 文件, 所有的零碎里,~/.bash_profile 都会有这样的逻辑,防止登陆时 ~/.bashrc 被跳过的状况:

# login shell will execute this
if [-n "$BASH_VERSION"]; then
    # include .bashrc if it exists
    if [-f "$HOME/.bashrc"]; then
        . "$HOME/.bashrc"
    fi
fi

在发行版的 Linux 零碎中,Interactive&Login 模式下的 ~/.bash_profile, ~/.bash_login,~/.profile 并不一定是三选一,看一下这三个脚本的内容会发现他们会持续调用下一个它想调用的配置文件,这样就能够防止配置项可能须要在不同的配置文件屡次配置。如 centos7.2 中 ~/.bash_profile 文件中理论调用了 ~/.bashrc 文件。

# .bash_profile

# Get the aliases and functions
if [-f ~/.bashrc]; then
    . ~/.bashrc
fi

# User specific environment and startup programs

PATH=$PATH:$HOME/.local/bin:$HOME/bin

export PATH

如上图所示,开启一个 Shell 过程时,有一些参数的值也会影响到配置文件的加载。如 –rcfile,–norc 等。

罕用的 shell 环境变量:

变量名 形容
PATH 命令搜寻门路,以冒号为分隔符
HOME 用户主目录的路径名,是 cd 命令的默认参数
SHELL 以后运行的 Shell 的全路径名
TERM 终端类型
LOGNAME 以后的登录名
PWD 当前工作目录
# 输入个别的环境变量值的两种形式

printenv HOME

echo $HOME

全局变量是对所有用户都须要应用的变量,能够将新的变量或批改过的变量设置放在 /etc/profile 文件中,但降级了发行版该文件也会更新,所以这点要留神(对所有用户)。

最好是在 /etc/profile.d 目录中创立一个以.sh 结尾的文件,把所有新的变量或批改过的变量全副放在此文件中(对所有用户)。

对于存储个人用户永久性 bash shell 变量的中央是 $HOME/.bashrc 文件。这一点实用于所有类型的 shell 过程(仅对以后用户)。

$*$@ 的区别

$*$@ 都示意传递给函数或脚本的所有参数,不被双引号(””)蕴含时,都是以 "$1" "$2" ... "\\$n" 模式把所有参数一个一个独自输入。

然而当他们被双引号蕴含是,"$*" 会将所有的参数作为一个整体,以 "$1 $2 ... $n" 的模式输入所有参数。"$@" 还是跟之前一样,把所有参数离开,一个一个的输入。

例如:./test.sh a b c d


#/bin/bash

echo "打印出没有引号的 $*"
for var in $*
do
echo "$var"
done
#输入:打印出没有引号的 $*
# a
# b
# c
# d

echo "打印出有引号的 \"$*\""for var in"$*"
do
echo "$var"
done
#输入:打印出有引号的 "$*"
# a b c d


echo "打印出没有引号的 $@"
for var in $@
do
echo "$var"
done
#输入:打印出没有引号的 $@
# a
# b
# c
# d

echo "打印出有引号的 \"$@\""for var in"$@"
do
echo "$var"
done
#输入:打印出有引号的 "$@"
# a
# b
# c
# d

Shell 中的替换

转义字符替换

应用 echo 命令时,应用 -e 能够对转义字符进行替换。应用 -E 能够禁止本义,默认也是不本义的;应用 -n 选项能够禁止插入换行符。

转义字符 含意
\b 退格(删除键)
\f 换页(FF),将以后地位移到下页结尾
\n 换行
\c 显示不换行
\r 回车
\t 程度制表符(tab 键)
\v 垂直制表符

#/bin/bash

a=1
b=2

echo -e "${a}\n${b}" #输入:1

# 2

命令替换

命令替换是指 Shell 能够先执行命令,将输入后果临时保留,在适当的中央输入。

命令替换的语法是:反引号 “。


#!/bin/bash

DATE=`date`
echo "日期是:\$DATE" #输入:日期是:Sun Oct 18 16:27:42 CST 2020

() 和 (())

先说一下 ()

在 bash 中,\$()与 “(反引号)都是用来作命令替换的。先实现引号里的命令行,而后将其后果替换进去,再重组成新的命令行。

相同点:\$() 与 “ 在操作上,这两者都是达到相应的成果

不同点:“ 很容易与 ” 搞凌乱,尤其对初学者来说,而 \$()比拟直观;不过 \$() 有兼容性问题,有些类 Unix 零碎不反对。

echo $(expr 1 + 2)

再说 (())

1、(()) 可间接用于整数计算

echo $((1 + 2))

2、(()) 可从新定义变量值,用于判断条件或计算等

#!/bin/bash

a=10
b=50

((a++))
echo $a
#输入:11

((a > b)) && echo "a > b"

((a < b)) && echo "a < b"

# 输入:a < b

3、(()) 可用于进制转换

\$(())能够将其余进制转成十进制数显示进去。语法:$((N#xx)),其中,N 为进制,xx 为该进制下某个数值,命令执行后能够失去该进制数转成十进制后的值。

echo $((2#110))
#输入:6
echo $((8#11))
#输入:9
echo $((16#1a))
#输入:26

test、[] 和 [[]]

type 命令查看

type "test" "[" "[["
#输入:#test is a shell builtin
#[ is a shell builtin
#[[is a reserved word

从下面能够看出,test[ 属于 Shell 的内置命令,[[属于 Shell 的保留关键字。

在应用上,test[ 是等价的,因为是命令,所以须要跟它的参数应用空格隔开。

test -f /etc/hosts && echo True
#输入:True

[-f /etc/hosts] && echo True
#输入:True

因为 ] 作为最初一个参数示意条件完结,而像 <> 符号会被了解为重定向,导致谬误

[1 < 2]
#输入:line 13: 2: No such file or directory

[[是关键字,可能依照惯例的语义了解其中的内容,双中括号中的表达式看作一个独自的语句,并返回其状态码。

[[1 < 2]] && echo True || echo False
#输入:True

举荐应用[[ 来进行各种判断,能够防止很多谬误。

如下展现单中括号会引发的谬误

[$a == 1 && $b == 1] && echo True || echo False
#输入:[: missing `]'

#例如 $a 为空,就会报语法错误,因为 [命令拿到的实际上只有 ==、1、] 三个参数
[$a == 1]
#输入:[: ==: unary operator expected

Here Document

Here Document 能够了解为“嵌入文档”。Here Document 是 Shell 中的一种非凡的重定向形式,它的根本模式如下:


command <<delimiter
document
delimiter

作用是将两个 delimiter 之间的内容 (document) 作为输出传递给 command。

留神:

  • 结尾的 delimiter 肯定要顶格写,后面不能有任何字符,前面也不能有任何字符,包含空格和 tab 缩进。
  • 开始的 delimiter 前后的空格会被疏忽掉。
#!/bin/bash

wc -l << EOF
line 1
line 2
line 3
EOF #输入:3

参考文档

  • Shell 中傻傻分不清楚的 TOP3
  • 千万别混同 Bash/Zsh 的四种运行模式
  • 一篇文章让你彻底把握 shell 语言
  • Bash 脚本 set 命令教程
  • linux shell 数据重定向(输出重定向与输入重定向)详细分析
  • linux shell 管道命令 (pipe) 应用及与 shell 重定向区别
  • Linux 零碎环境变量地位,环境变量长久化
退出移动版