个人博客原文连接:https://chenfangxu.lap.360.cn/assistive-tools/shell/script.htmlhtml
Shell 脚本(shell script),是一种为 Shell 编写的脚本程序,通常文件后缀为 .sh
。linux
#!
是一个约定的标记,它告诉系统这个脚本须要什么解释器来运行,即:使用哪种 shell。#!
被称为shebang(也称为 Hashbang),例如使用 bash:#! /bin/bash
shell
新建一个 test.sh 的文件,内容以下:express
#!/bin/bash echo "Hello World!"
一、当前 test.sh 是没有可执行权限的,首先使脚本文件具备执行权限。npm
# 使脚本文件具备执行权限 chmod +x ./test.sh
二、执行脚本centos
# 执行脚本,需注意要加目录的标识 ./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,能够省去第一行。bash
直接运行解释器,其参数就是 Shell 脚本的文件名。app
/bin/bash test.sh
这种方式运行的脚本,不须要在第一行指定解释器信息,写了也没用。ssh
#
开头,到行尾结束。:<<EOF
开头,到 EOF
结束# 这是单行注释 :<<EOF 这是多行注释 这是多行注释 EOF
若是有段代码要频繁的注释和取消注释,能够用花括号括起来,定义成一个函数,没有地方调用这个函数,这块代码就不会执行,达到了和注释同样的效果。
export
关键字,shell 脚本也能够定义环境变量。一、声明变量
可使用等号操做符为变量赋值:varName=value
,varName 是变量名,value 是赋值给变量的值。
变量名的命名规则:
#!/bin/bash fruit=apple count=5
注意:varName=value
的等号两边没有空格,变量值若是有空格,须要用引号包住。
二、访问变量
访问变量的语法形式为:${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"
会显示出该变量的值,若是没有定义过,则什么都不显示。
三、只读变量
使用 readonly 命令能够将变量定义为只读变量,只读变量的值不能被改变
#!/bin/bash fruit=apple echo $fruit readonly fruit #fruit=banana #若是放开注释,执行时会报错
四、删除变量
使用 unset 命令能够删除变量,变量被删除后不能再次使用。unset 命令不能删除只读变量
#!/bin/bash fruit=apple echo $fruit #输出: apple unset fruit echo $fruit #输出: (空)
上面讲过变量名的命名规则,可是还有一些包含其余字符的变量有特殊含义,这样的变量被称为特殊变量。
变量 | 含义 |
---|---|
$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。$?
也能够表示函数的返回值。
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
echo ${#file} # 输出:27
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
echo ${var:-"var is not set"} #输出:var is not set echo "var is ${var}" #此时 var 仍是没有定义,因此输出:var is
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 是否能够被正常赋值。若此替换出如今 shell 脚本中,那么脚本将中止运行。
数组是能够存储多个值的变量,这些值能够单独引用,也能够做为整个数组来引用。数组的下标从 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
[${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 [ 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 [ expression ] then expression 是 true ,这里会被执行 else expression 是 false , 这里会被执行 fi
#!/bin/bash if [ 1 = 2 ] then echo "相等" else echo "不相等" 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 与其余语言中的 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 变量 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 循环会不断的检测一个条件,只要这个条件返回 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 循环是检测一个条件,只要条件是 false 就会一直执行循环,直到条件条件为 true 时中止。它跟 while 正好相反。
#!/bin/bash x=0 until [ ${x} -eq 5 ]; do echo ${x} x=`expr ${x} + 1` done
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 命令后面还能够跟一个整数,表示跳出几层循环。
#!/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 命令跟 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
function
关键字无关紧要$?
得到。语法:中括号内表示可选 [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
shell 提供了用于 debug 脚本的工具。若是想采用 debug 模式运行某脚本,能够在其 shebang 中使用一个特殊的选项。(有些 shell 不支持)
#!/bin/bash [options]
或者在执行 Bash 脚本的时候,从命令行传入这些参数
bash -euxo pipefail test.sh
有时咱们只须要 debug 脚本的一部分。这种状况下,使用 set 命令会很方便。这个命令能够启用或禁用选项。 使用 -
启用选项,使用 +
禁用选项。
一、用来在运行结果以前,先输出执行的那一行命令
set -x #或者 set -o xtrace
二、执行脚本时,若是遇到不存在的变量会报错,并中止执行。(默认是忽略报错的)
set -u #或者 set -o nounset
顺便说一下,若是命令行下不带任何参数,直接运行set
,会显示全部的环境变量和 Shell 函数。
三、执行脚本时,发生错误就终止执行。(默认是继续执行的)
set -e #或者 set -o errexit #能够用下面是方法 command || exit 1 #或者 command1 && command2
set -e
根据返回值来判断,一个命令是否运行失败。可是,某些命令的非零返回值可能不表示失败,或者开发者但愿在命令失败的状况下,脚本继续执行下去。这时能够暂时关闭 set -e,该命令执行结束后,再从新打开 set -e。
set +e command1 command2 set -e #也能够用下面的方法 command || true
四、管道命令执行失败,脚本终止执行
管道命令就是多个子命令经过管道运算符(|
)组合成为一个大的命令。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 模式有关。
检测当前 shell 运行的环境是否是 Interactive 模式
[[ $- == *i* ]] && echo "Interactive" || echo "Non-interactive"
/etc/passwd
下用户所属的 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 等。
如上图加载顺序所示
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
使用 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)
一、(()) 可直接用于整数计算
echo $((1 + 2))
二、(()) 可从新定义变量值,用于判断条件或计算等
#!/bin/bash a=10 b=50 ((a++)) echo $a #输出:11 ((a > b)) && echo "a > b" ((a < b)) && echo "a < b" # 输出:a < b
三、(()) 可用于进制转换
\$(())能够将其余进制转成十进制数显示出来。语法:$((N#xx))
,其中,N 为进制,xx 为该进制下某个数值,命令执行后能够获得该进制数转成十进制后的值。
echo $((2#110)) #输出:6 echo $((8#11)) #输出:9 echo $((16#1a)) #输出:26
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为空,就会报语法错误,由于 [ 命令拿到的实际上只有 ==、一、] 三个参数 [ $a == 1 ] #输出:[: ==: unary operator expected
Here Document 能够理解为“嵌入文档”。Here Document 是 Shell 中的一种特殊的重定向方式,它的基本形式以下:
command <<delimiter document delimiter
做用是将两个 delimiter 之间的内容(document)做为输入传递给 command。
注意:
#!/bin/bash wc -l << EOF line 1 line 2 line 3 EOF #输出:3