参考自https://www.runoob.com/linux/linux-shell.html
运行shell脚本
作为可执行程序,
./test.sh
, 注意添加#!/bin/bash
标记, 打开一个子shell来读取并执行test.sh
中的命令, 需要”执行权限”作为解释器参数
bash test.sh
, 打开一个子shell来读取并执行test.sh
中的命令, 可以无”执行权限”source命令,
source test.sh
, 在当前bash环境下读取并执行test.sh
中的命令, 可以无”执行权限”
shell变量
赋值
- 显式赋值
注意: 变量名和等号之间不能有空格
your_name="didong"
- 语句赋值
1 | for file in `ls /etc` |
使用
1 | your_name="didong" |
注意: 花括号可选, 但花括号可以用来识别变量边界, 比如:
1 | for skill in Ada Coffe Action Java; do |
只读变量
readonly your_name
, 将变量设为只读
删除变量
unset your_name
, 删除变量, 但不能删除只读变量
变量类型
- 局部变量
在脚本或命令中定义, 仅在当前shell示例中有效, 其他shell启动的程序不能访问局部变量
- 环境变量
所有的程序, 包括shell启动的程序, 都能访问环境变量, 有些程序需要环境变量来保证其正常运行; 必要的时候shell脚本也可以定义环境变量.
- shell变量
shell变量是由shell程序设置的特殊变量; shell变量中有一部分是环境变量, 有一部分是局部变量, 这些变量保证了shell的正常运行
字符串
单引号
限制:
- 单引号里的任何字符都会原样输出, 单引号字符串中的变量是无效的
- 单引号字串中不能出现单独一个单引号(对单引号使用转义符后也不行), 但可成对出现, 作为字符串拼接使用
双引号
优点:
- 双引号里可以有变量
- 双引号里可以出现转义字符
拼接字符串
1 | your_name="runoob" |
输出结果:
1 | hello, runoob ! hello, runoob ! |
获取字符串长度
1 | string="abcd" |
或使用length:
1 | string="hello, everyone my name is didong" |
使用expr命令时, 表达式中的运算符左右必须包含空格, 如果不包含空格, 将会输出表达式本身
对于某些运算符, 还需要我们使用\
进行转义, 否则会提示语法错误, 如*
expr命令
expr命令是一个手工命令行计数器, 用于求表达式变量的值, 一般用于整数值, 也可用于字符串
语法: expr 表达式
详细内容直接man
提取子字符串
从字符串第 2 个字符开始截取 4 个字符(从0开始计数):
1 | string="runoob is a great site" |
字符串截取
设有变量var=http://qiniu.wangqy.top/didong/images/
- #号截取, 删除左边字符, 保留右边字符
echo ${var#*//}
从左边开始删除第一个//号及左边的所有字符
- ##号截取, 删除左边字符, 保留右边字符
echo ${var##*/}
从左边开始删除最后(最右边)一个/号及左边所有字符
- %号截取, 删除右边字符, 保留左边字符
echo ${var%/*}
从左边开始删除第一个/号及右边字符
- %%号截取, 删除右边字符, 保留左边字符
echo ${var%%/*}
从左边开始, 删除最后(最左边)一个/号及右边的字符
查找子字符
查找字符 i 或 o 的位置(哪个字母先出现就计算哪个):
1 | string="runoob is a great site" |
注意: 这里的输出是从 1 开始计数
数组
只支持一维数组, 不支持多位数组, 并且没有限定数组的大小, 数组下标由0开始
定义
1 | array_name=(value0 value1 value2 value3) |
下标的范围没有限制
读取
1 | valuen=${array_name[n]} # 单个元素 |
获取数组长度
1 | # 取得数组元素的个数 |
Bourne shell (原生kernel下) 下不支持数组, 只能通过模拟来实现数组功能
1 |
|
eval函数
语法: eval cmdline
eval会对后面的cmdline进行两遍扫描, 如果第一遍扫描后, cmdline是个普通命令, 则执行此命令; 如果cmdline中含有变量的间接引用, 则保证间接引用的语义
注释
单行
1 | # 这是注释 |
多行
可以把这一段要注释的代码用一对花括号括起来,定义成一个函数,没有地方调用这个函数,这块代码就不会执行,达到了和注释一样的效果
如下:
1 | :<<EOF |
read命令用于获取键盘输入信息
read [-options] [variable...]
read命令一个一个词组地接受输入的参数, 每个词组需要使用空格进行分隔; 如果输入的词组个数大于需要的参数个数, 则多出的词组将被作为整体为最后一个参数接收
参数 | 说明
-p | 输入提示文字
-n | 输入字符长度限制
-t | 输入限时
-s | 隐藏输入内容
1 | read -p "请输入一段文字:" -n 6 -t 5 -s password |
输出结果:
1 | 请输入一段文字: |
传递参数
传递参数: 执行shell脚本时, 向脚本传递参数
获取参数:$n
, 0为脚本文件名, 1为第一个参数, 2为第二个参数, 以此类推…
特殊参数处理字符
参数处理 | 说明 |
---|---|
$# | 传递到脚本的参数个数 |
$* | 以一个单字符串显示所有向脚本传递的参数 |
$$ | 脚本运行的当前进程id号 |
$! | 后台运行的最后一个进程的id号 |
$@ | 与$*相同, 但使用时加引号, 并在引号中返回每个参数 |
$- | 显示shell使用的当前选项, 与set命令功能相同 |
$? | 显示最后命令的退出状态, 0表示没有错误, 其他任何值表示有错误 |
注意: 传递的参数中如果包含空格, 应该使用单引号或者双引号将该参数括起来, 以便于脚本将这个参数作为整体来接收
中括号的用法
[]
常常可以使用test命令来代替
[
与]
与操作数之间一定要有一个空格, 否则会报错
算数比较
1 | [ $var -eq 0 ] # 当 $var 等于 0 时, 返回真 |
其他比较操作符:
操作符 | 说明 |
---|---|
-gt | 大于 |
-lt | 小于 |
-ge | 大于或等于 |
-le | 小于或等于 |
文件系统属性测试
操作符 | 说明
[ -f $file_var ] | 变量 $file_var 是一个正常的文件路径或文件名 (file), 则返回真
[ -x $var ] | 变量 $var 包含的文件可执行 (execute), 则返回真
[ -d $var ] | 变量 $var 包含的文件是目录 (directory), 则返回真
[ -e $var ] | 变量 $var 包含的文件存在 (exist), 则返回真
[ -c $var ] | 变量 $var 包含的文件是一个字符设备文件的路径 (character), 则返回真
[ -b $var ] | 变量 $var 包含的文件是一个块设备文件的路径 (block), 则返回真
[ -w $var ] | 变量 $var 包含的文件可写(write), 则返回真
[ -r $var ] | 变量 $var 包含的文件可读 (read), 则返回真
[ -L $var ] | 变量 $var 包含是一个符号链接 (link), 则返回真
字符串比较
在进行字符串比较时, 最好使用双中括号[[ ]]
, 因为单中括号可能会导致一些错误, 因此最好避开它们
操作符 | 说明 |
---|---|
[[ $str1 = $str2 ]] | 当 str1等于str2 时返回真, = 前后有空格, 如果不加空格, 就变成了赋值语句 |
[[ $str1 == $str2 ]] | 同上 |
[[ $str1 != $str2 ]] | 如果 str1 与 str2 不相同, 则返回真 |
[[ -z $str1 ]] | 如果 str1 是空字符串, 则返回真 |
[[ -n $str1 ]] | 如果 str1 是非空字符串, 则返回真 |
组合
中括号中
可以通过-a(and)或-o(or)结合多个条件进行测试:
1 | [ $var1 -ne 0 -a $var2 -gt 2 ] # 使用逻辑与 -a |
中括号外
使用逻辑运算符&&
和||
将多个条件组合起来:
1 | str1="Not empty" |
test命令
test命令也可以执行条件检测, 用test可以避免使用过多的括号, 如:
if [ $var -eq 0 ]; then echo "True"; fi
等价于:
if test $var -eq 0; then echo "True"; fi
set命令
功能
用于设置shell的执行方式
语法
set [+-abCdefhHklmnpPtuvx]
参数说明
参数 | 说明 |
---|---|
a | 标示已修改的变量, 以供输出至环境变量 |
b | 使被中止的后台程序立刻回报执行状态 |
C | 转向所产生的文件无法覆盖已存在的文件 |
d | Shell预设会用杂凑表记忆使用过的指令, 以加速指令的执行; 使用-d参数可取消 |
e | 若指令回传值不等于0, 则立即退出shell |
f | 取消使用通配符 |
h | 自动记录函数的所在位置 |
H | 可利用! 加<指令编号>的方式来执行history中记录的指令 |
k | 指令所给的参数都会被视为此指令的环境变量 |
l | 记录for循环的变量名称 |
m | 使用监视模式 |
n | 只读取指令, 而不实际执行 |
p | 启动优先顺序模式 |
P | 执行指令时, 会以实际的文件或目录来取代符号连接 |
t | 执行完随后的指令, 即退出shell |
u | 当执行时使用到未定义过的变量, 则显示错误信息 |
v | 显示shell所读取的输入值 |
x | 执行指令后, 会显示该指令及所下的参数 |
+<参数> | 取消某个set曾启动的参数 |
基本运算符
原生bash不支持简单的数学运算, 但是可以通过其他命令来实现, 如awk和expr, expr最常用
算数运算符
+
-
*
/
%
=
==
!=
关系运算符
关系运算符只支持数字, 不支持字符串, 除非字符串的值是数值
参照算数比较部分
布尔运算符
注: 变量a为10, b为20
运算符 | 说明 | 举例 |
---|---|---|
! | 非运算 | [ ! false ] 返回 true |
-o | 或运算 | [ $a -lt 20 -o $b -gt 100 ] 返回 true |
-a | 与运算 | [ $a -lt 20 -a $b -gt 100 ] 返回 false |
逻辑运算符
运算符 | 说明 |
---|---|
&& | 逻辑的AND |
|| | 逻辑的OR |
字符串运算符
变量a为”abc”, b为”efg”
运算符 | 说明 | 举例
= | 相等 | [ $a = $b ] 返回 false
!= | 不相等 | [$a != $b ] 返回 true
-z | 检测字符串长度是否为0, 为0返回true | [ -z $a ] 返回 false
-n | 检测字符串长度是否为0, 不为0返回true | [ -n $a ]返回 true
$ | 检测字符串是否为空, 不为空返回true | [ $a ]返回 true
文件测试运算符
用于检测Unix文件的各种属性
操作符 | 说明
-b file | 检测文件是否是块设备, 如果是, 则返回true
-c file | 检测文件是否是字符设备文件, 如果是, 则返回true
-d file | 检测文件是否是目录, 如果是, 则返回true
-f file | 检测文件是否是普通文件(既不是目录也不是设备文件), 如果是, 则返回true
-g file | 检测文件是否设置了SGID位, 如果是, 则返回true
-k file | 检测文件是否设置了粘着位(Sticky Bit), 如果是, 则返回true
-p file | 检测文件是否是有名管道, 如果是, 则返回true
-u file | 检测文件是否设置了SUID位, 如果是, 则返回true
-r file | 检测文件是否可读, 如果是, 则返回true
-w file | 检测文件是否可写, 如果是, 则返回true
-x file | 检测文件是否可执行, 如果是, 则返回true
-s file | 检测文件是否为空(文件大小是否大于0), 不为空返回true
-e file | 检测文件(包括目录)是否存在, 如果是, 则返回true
-S file | 判断某文件是否socket
-L file | 检测文件是否存在并且是一个符号链接
其他
使用
[[ ... ]]
条件判断结构, 而不是[ ... ]
, 能够防止脚本中的许多逻辑错误. 比如,&&
,||
,<
和>
操作附能够正常存在于[[]]
条件判断结构中, 但是如果出现在[]
结构中的话, 会报错
使用
[]
时,>
和<
需要使用反斜线转义
相加的写法
1.
1 | a=10 |
2.
1 | c=${ `expr 10 + 20` } |
3.
1 | c=$[ 10 + 20] |
4.
1 | a=10 |
echo命令
- 显示普通字符串
echo "It is a test"
双引号可省略
echo It is a test
- 显示转义字符
echo "\"It is a test\""
结果为
"It is a test"
同样, 双引号也可以省略
- 显示变量
read 命令从标准输入中读取一行, 并把输入行的每个字段的值指定给shell变量
1 | read name |
- 显示换行
1 | echo -e "OK! \n" # -e 开启转义 |
输出结果:
1 | OK! |
- 显示不换行
1 | echo -e "OK! \c" |
输出结果:
1 | OK! It is a test |
- 显示结果定向至文件
1 | echo "It is a test" > myfile |
- 原样输出字符串, 不进行转义或取变量(用单引号)
1 | echo '$name\"' |
输出结果:
1 | $name\" |
- 显示命令执行结果(反引号)
1 | echo `date` |
结果将显示当前日期
总结
能否引用变量 | 能否引用转义符 | 能否引用文本格式符(如: 换行符, 制表符)
单引号 | 否 | 否 | 否
双引号 | 能 | 能 | 能
无引号 | 能 | 能 | 否
printf命令
printf由POSIX标准所定义, 因此使用printf的脚本比使用echo移植性好
printf使用引用文本或空格分割的参数
语法; printf format-string [arguments...]
具体格式化字符串参照C中的printf()函数
注意:
- 单引号与双引号效果一样
- 没有引号也可以输出
- 格式只指定了一个参数, 但多出的参数仍然会安装该格式输出, format-string被重用
- 如果没有arguments, 那么 %s 用 NULL 代替, %d 用 0 代替
流程控制
和Java, PHP等语言不一样, sh的流程控制不可为空, 如果else分支没有语句执行, 就不要写这个else
if else
if
1 | if condition |
也可以写成一行(每行命令末尾要加分号)
if else
1 | if condition |
if else-if else
1 | if condition |
for循环
1 | for var in item1 item2 ... itemN |
也可以写成一行(每行命令末尾要加分号)
类似于C中的for循环
1 | for((assignment;condition;next)); |
注意: 如果要在循环体中进行for中的next操作, 记得变量要加$
, 不然程序会变成死循环
while语句
1 | while condition |
let命令
let命令是BASH中用于计算的工具, 用于执行一个或多个表达式, 变量计算中不需要加上$
来表示变量. 如果表达式中包含了空格或其他特殊字符, 则必须引起来.
语法格式: let arg [arg ...]
, 其中arg为要执行的表达式
与C/C++表达式类似
无限循环
1 | while : |
或者
1 | while true |
或者
1 | for (( ; ; )) |
until循环
until循环执行一系列命令直至条件为true
时停止, while循环执行一系列命令直至条件为false
时停止
1 | until condition |
case 语句
1 | case 值 in |
如果没有匹配的模式, 使用*
捕获该值
跳出循环
break
continue
shell函数
1 | [ function ] funname [()] |
说明:
- 可以带
function funname()
定义, 也可以直接funname()
定义, 不带任何参数- 参数返回, 可以显示加
return
返回, 如果不加, 将以最后一条命令运行结果, 作为返回值, return后跟数值n(o-255)
funname
调用函数
输入/输出重定向
命令 | 说明 |
---|---|
command > file | 将输出重定向到file |
command < file | 将输入重定向到file |
command >> file | 将输出以追加的方式重定向到file |
n > file | 将文件描述符为n的文件重定向到file |
n >> file | 将文件描述符为n的文件以追加的方式重定向到file |
n >& m | 将输出文件m和n合并 |
n <& m | 将输入文件m和n合并 |
<< tag | 将开始标记tag和结束标记tag之间的内容作为输入 |
文件描述符0通常是标准输入(STDIN), 1是标准输出(STDOUT), 2是标准错误输出(STDERR)
command1 < infile > outfile
同时替换输入和输出, 执行command1, 从文件infile读取内容, 然后将输出写入到outfile中
如果希望stderr重定向到file
command 2 > file
如果希望stderr追加到file文件末尾command 2 > file
如果希望将stdout和stderr合并后重定向到file
command > file 2>&1
或者command >> file 2>&1
Here Document
1 | command << delimiter |
将两个delimiter之间的内容(documnet)作为输入传递给command
结尾的delimiter一定要顶格写, 前面不能有任何字符, 后面也不能有任何字符, 包括空格和tab缩进
开始的delimiter前后的空格会被忽略掉
示例:
1 | wc -l << EOF |
/dev/null文件
如果希望执行某个命令, 但又不希望在屏幕上显示输出结果, 那么可以将输出重定向到
/dev/null
command > /dev/null
/dev/null
是一个特殊的文件, 写入到它的内容都会被丢弃; 如果尝试从该文件读取内容, 什么也读不到
如果希望屏蔽stdout和stderrcommand > /dev/null 2>&1
顺序问题
1 | find /etc -name .bashrc > list 2>&1 |
第一种
先要将输出到stdout的内容重定向到文件, 此时文件list就是这个程序的stdout, 再将stderr重定向到stdout, 也就是文件list
第二种
先将要输出的stderr的内容重定向到stdout, 此时会产生一个stdout拷贝
, 作为程序的stderr, 而程序原本要输出到stdout的内容, 依然是对接在stdout原身上的, 因此第二步重定向stdout, 对stdout的拷贝不产生任何影响
其他问题
直接在FreeBSD或者csh中使用
command > file 2>&1
的时候会得到这个错误:Ambiguous output redirect
. 出错的原因在于FreeBSD默认使用csh, 在csh中如果想把标准输出和错误输出同时重定向到一个文件, 需要用command >& file
shell文件包含
包含外部脚本, 方便封装一些公用的代码作为一个独立的文件
1 | . filename |
被包含的文件不需要可执行权限