退出码
linux 每个命令都使用退出状态码(exit status) 来告诉shell 该命令已经完成。每次命令执行后返回的退出状态码都会用$?
变量保存。
$ ls
...
$ echo $?
0
linux 的退出状态码一般有以下种类:
0 命令成功结束
1 一般性未知错误
2 不适合的shell 命令
126 命令不可执行
127 为没找到命令
128 无效的退出参数
128+x 与linux 信号x 相关的严重错误
130 通过ctrl+c 终止的命令
255 正常范围外的退出码
另外,我们也可以在脚本的末尾指定exit xxx
,设定xxx 使得该脚本返回指定的退出码。但退出码的范围为0-255
,因此如果退出码为大于该区间的数,则shell 会通过模运算取余。如300,则会返回44,300%256=44
if 条件句
shell 中的if 与其他编程语言有所不同。它默认下的if 后的对象为一个命令(command),而非通常的条件(condition)。
if command1
then
command2
elif command3
then
command4
else command5
if command6
then
command7
fi
fi
- 通常if条件句的执行顺序为优先执行
if
中的命令,若该命令的退出码为0(成功结束),则执行紧跟if
的then
中的命令。
若返回非零退出码,则会执行else
部分。 elif
则相当于else + if + then
,若elif
后命令的退出码为0,则会继续执行elif
后面的语句。- 但需要注意的是,
elif
实际相当于一个新的if
语句,因此elif
后面的then
与else
是相对于elif
的退出码来执行的:为0则执行then
,非零则执行else
- 有时候在执行了一次条件之后,还可以使用嵌套if,检查更多的条件。需要注意的是,每一次调用
if
,都需要在当前的结尾处使用fi
,声明if-then
语句的结束。
test 命令
test 命令就有点像一般编程语句的默认if 选项,提供了条件判断的途径。如果test 条件成立,则会返回退出码0。
if test condition1
if [ condition2 ]
⚠️注意:中括号与条件之间需要间隔一个空格。
一共有以上两种条件测试的方法。
test 提供了三类条件的判断:
- 数值比较
- 字符串比较
- 文件比较
数值比较
shell 的数值比较使用的比较操作符与字符串是不一样的。(和perl 恰恰相反)
perl 中的命令:
$ cat test.perl
#!/usr/bin/perl
if('as' gt 'a'){
print "hello!\n";
}
输出
$ perl test.perl
hello!
shell 脚本中的命令:
$ cat test2
#!/bin/bash
if [ 54 -gt 44 ]
then echo -e "hello!"
fi
输出
$ test2
hello!
需要注意的是,bash shell 只能处理整数,也即如果语句中出现浮点值,则shell 会报错。
$ cat test3
#!/bin/bash
if [ 54.22 -gt 44 ]
then echo -e "hello!"
fi
$ test3
/tmp/test/test3: line 3: [: 54.22: integer expression expected
总结一下:
字符串比较
str1 = str2 # 相等
str1 != str2
str1 < str2 # 小于
str1 > str2
-n str1 # 检查str1 长度是否非0
-z str1 # 检查str1 长度是否为0
- 需要注意的是,
>
与<
在shell 中有重定向之意,因此在实际比较中需要使用转义符\
if [ $var1 \> $var2 ]
then command
另外,字符串的比较顺序和sort
命令相反。主要体现在两点:
- 较长的字符串小于较短的字符串;
- 大写字母小于小写字母。
这是因为比较测试采用的是ASCII 顺序,因此大写字母出现在小写字母之前。
此外,-n与-z 选项非常重要,可以用来在操作数值或字符比较前用于确定其是否为空。
小注意事项
test 命令的数值比较与字符串比较使用不同的比较符号进行比较。因此在使用时需要注意,如果使用了字符串比较符号对数值进行比较,则shell 会将数字当作字符串值进行运算,会对结果造成影响。
文件比较
文件比较是比较测试中最丰富的类型。
-d file # 检查file 是否为一个目录
-e file # 检查file 是否存在
-f file # 是否为文件
-r file # 是否可读
-s file # 是否非空
-w file # 是否可写
-x file # 是否可执行
-O file # 是否属于当前用户
-G file # 默认组是否与当前用户相同
file1 -nt file2 # 判断文件哪个更新
file1 -ot file2 # 判断文件哪个更旧
! # 条件前面加!表示取反
复合条件测试
shell 提供了两种布尔运算符实现复合条件测试。
[ condition1 ] && [ condition2 ] # AND,需要满足两个条件才返回0
[ condition3 ] || [ condition4 ] # OR,满足其中之一即可返回0
高级特性
高级数学表达式
可以使用双括号,实现高级的数学表达式。同样地,需要在括号与语句之间,保留空格。
(( expression/condition ))
这样的表达使得数学赋值与比较变得更加灵活。
比如单方括号的条件测试,是无法执行多命令与数学运算的。
$ cat test2
#!/bin/bash
if [ 54 ** 2 -gt 44 ]
then echo -e "hello!"
fi
$ test2
/tmp/test/test2: line 3: [: too many arguments
改成双括号即可。
$ cat test2
#!/bin/bash
if (( 54 ** 2 > 44 ))
then echo -e "hello!"
fi
$ test2
hello!
需要注意,此时的数值运算就不再使用文本比较符号
了。另外,双括号中的符号无需转义
。
双括号命令符号
val++ # 后增
val--
++val # 先增
--val
! # 逻辑求反
~ # 位求反
** # 幂
<< # 左位移
>>
& # 位布尔和
| # 位布尔或
&& # 逻辑和
|| # 逻辑或
其中需要注意的是:
后增与先增
$ cat test2
#!/bin/bash
var1=1
var2=1
if (( ++var1 > var2++ ))
then echo -e "hello! And $var1, $var2"
fi
$ test2
hello! And 2, 2
虽然二者都执行同样的运算,且总结果来看,二者都有了一致的结果。但从then 被执行可知,if 语句的退出状态码为0,因此表达式成立。
而先增与后增也就在于,先增为在当前语句执行前就进行了增加(if 判断前),而后增则是if 判断完成后再进行增加。因此2>1,也就是条件成立。
另外,在进行先/后 增/减
表达时,需要将变量的$
去掉。
$ cat test2
#!/bin/bash
var1=1
var2=1
if (( $++var1 > $var2++ ))
then echo -e "hello! And $var1, $var2"
fi
$ test2
/tmp/test/test2: line 5: ((: $++var1 > 1++ : syntax error: operand expected (error token is "$++var1 > 1++ ")
位运算
位运算在linux 中主要基于内存中二进制数据的运算,位元素的移动也就是某一位上0或1 的移动。
高级字符串表达式
可以使用双方括号,实现高级的字符串表达式。同样地,需要在括号与语句之间,保留空格。(ps:bash支持双方括号,但并非所有的shell 都支持。)
[[ expression/condition ]]
与双括号不同,双方括号内的符号和test 标准字符串比较符号一致。
双方括号的高级之处,就在于它具备了模式匹配,pattern matching,的特性。
在模式匹配下,我们可以使用正则表达式来来匹配字符串值。[[ $a == r* ]]
表示,判定左边的变量是否符合右边的字符串模式(以r开头的任意字符串),如果符合的话,则退出码为0。
case 命令
如果需要对不同的条件进行匹配,使用if-elif… 则显得有些麻烦。
可以直接用case命令,实现模式匹配。
case $var in
a | b)
echo 'hello.';;
c)
echo 'good bye.';;
esac
for 循环
for var in list
do
commands
done
当list 中的元素包含特殊字符或空格时,可以直接使用 双引号
分割每个元素,或者用转义字符转义。
for 循环也可以从命令中读取列表。
for var in $(cat $file)
do
echo $var
done
for 循环还可以用于重命名(单引号内的代码运行优先级更高)
修改字段分隔符
当使用循环遍历文本文件时,有时候遇到下面情况。
如果我们想要遍历每行。却因为空格,拆分了本来一行中的内容。
cat ifs_test.txt
a b
c
d
$ for i in $(cat ifs_test.txt)
> do
> echo $i
> done
a
b
c
d
这是因为shell中存在特殊的环境变量 IFS
,叫做 内部字符分隔符
。
默认下shell 的分隔符包括:
空格、制表符、换行符。
因此如果想要解决上面的问题,就需要修改IFS。IFS=$'\n'
,使shell 只能识别到换行符作为分割点。
C语言风格的for循环
while 循环
更推荐右侧的语法(左侧也是正确的)。
参数拓展
掐头
替换
$ ls
test_1 test_10 test_2 test_3 test_4 test_5 test_6 test_7 test_8 test_9
$ ls test* | while read id; do mv ${id} ${id/_/_file_}; done
$ ls
test_file_1 test_file_2 test_file_4 test_file_6 test_file_8
test_file_10 test_file_3 test_file_5 test_file_7 test_file_9
截取
练习
注:把每一题的用到的若干命令都粘贴到对应的代码框中
- 软件安装与环境变量:请安装一个二进制软件 bowtie2,包括下载、解压、调用 bowtie2 命令的帮助文档,修改环境变量 PATH
不需要下载这个软件,因为服务器上已经有了,拷贝一份即可 /home/hcguo/tmp/biosoft/bowtie2-2.3.4.3-linux-x86_64.zip
# 提示,添加环境变量需要改一下安装路径
echo 'export PATH="安装路径:$PATH"' >> ~/.bashrc
- if 语句与状态参数:在上一题修改好了环境变量之后,回到家目录,再次调用 bowtie2 命令的帮助文档,然后使用 if 语句判断是否调用成功(提示:状态参数变量),如果成功,输出:yes ,否则输出 no
$ if [ $? -eq 0 ]; then echo success; else echo fail; fi
success
- if 语句与自定义变量:
- 创建文件 file1
- 然后判断当前目录下是否有 file1 这个文件
- 如果是,输出:yes ;否则什么都不做
~
$ touch file1
~
$ if [ -f file1 ]
> then echo yes
> fi
yes
- 创建 10 个文件 test_file_1 ~ test_file_10 ,使用循环语句(建议用 while read id),将上面的 test_file_1 ~ test_file_10 进行重命名,如:test_file_1 改为 test_1
$ ls test* | while read id; do mv ${id} ${id/_file/}; done