linux之shell语法基础

Shell脚本和编程语言很相似,也有变量和流程控制语句,但Shell脚本是解释执行的,不需要编译,Shell程序从脚本中一行一行读取并执行这些命令,相当于一个用户把脚本中的命令一行一行敲到Shell提示符下执行。

脚本执行:

1
2
chmod a+x test.sh
./test.sh

Shell脚本中用#表示注释,但第一行开头的#!则例外,它表示该脚本使用后面指定的解释器/bin/sh解释执行。

变量定义与调用

环境变量与本地变量

按照惯例,Shell变量由全大写字母加下划线组成,有两种类型的Shell变量:

  • 环境变量
    环境变量可以从父进程传给子进程。
  • 本地变量
    只存在于当前Shell进程。定义或赋值一个变量时,等号两边都不能有空格。
    1
    VARNAME=value
    一个变量定义后仅存在于当前Shell进程,它是本地变量,用export命令可以把本地变量导出为环境变量,定义和导出环境变量通常可以一步完成,也可分两步完成:
    1
    2
    3
    export VARNAME=value
    PATH="/home/bin/"
    export PATH
    用unset命令可以删除已定义的环境变量或本地变量。
    1
    unset VARNAME
    Shell变量不需要明确定义类型,事实上Shell变量的值都是字符串。
    注意,在定义变量时不用$,取变量值时要用$。用${VARNAME}可以表示它的值,在不引起歧义的情况下也可以用$VARNAME表示它的值。

    外部参数变量、自定义变量与列变量

    Shell语言的变量调用,有外部参数变量$1,内部自定义变量$variation,还有行和列变量$2。

传入外部参数给脚本:例如./extract.sh Pt1 O1
$0 脚本本身
$1 第一个参数,值为Pt1
$2 第二个参数,值为O1

awk列模式变量 $1, $2, … 表示第一列,第二列,,,,注意只在awk内部起作用

sed调用

sed一般使用单引号,sed引用shell变量时使用双引号即可,因为双引号是弱转义,不会去除$的变量表示功能,而单引号为强转义,会把$作为一般符号表示,所以不会表示为变量。

1
sed -i "s/$pattern1/$pattern2/g" inputfile

awk调用

awk在调用shell变量时,需要使用参数-v,举例说明:

1
awk  -v b=$count '{sum += $1};END {print sum/b}'  filename

其中$count为自定义变量,每调用一次变量都要加一个-v参数,后面使用自定义变量的时候,就不用加$了,直接是b就好了;$1是列变量,只存在于awk命令的单引号中。

引号与通配符

通配符

  • * 匹配0个或多个任意字符
  • ? 匹配一个任意字符
  • [若干字符] 匹配方括号中任意一个字符的一次出现
1
2
3
4
cp test* ../newfile/  # 匹配以test开头的文件
cp test?.sh ../
cp test[0-2].sh ../
cp test[012].py ../

命令代换与算术代换

  • 由`反引号括起来的也是一条命令,Shell先执行该命令,然后将输出结果立刻代换到当前命令行中;也可以用$()表示
    1
    2
    3
    4
    DATE=`date`
    echo $DATE

    DATE=$(date)
  • $(())或$[]中的Shell变量取值将转换成整数,只能用+-*/和()运算符,并且只能做整数运算。
    1
    2
    3
    VAR=21
    echo $(($VAR+3))
    echo $[3*5+6]

单引号与双引号

Shell脚本中的单引号和双引号一样都是字符串的界定符,单引号用于保持引号内所有字符的字面值,即使引号内的\和回车也不例外,但是字符串中不能出现单引号。

grep命令

grep命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来。
grep [options] pattern file

[options]主要参数:

正则表达式参数控制

-E:扩展正则表达式,不用”\”号进行转义

1
2
3
grep ‘w\(es\)t.*\1′ aa 
# 如果west被匹配,则es就被存储到内存中,并标记为1,然后搜索任意个字符(.*),这些字符后面紧跟着 另外一个es(\1),找到就显示该行。
grep -E 'w(es)t.*\1' aa # 不用”\”号进行转义

-i:不区分大小写。
-w :只匹配整个单词,而不是字符串的一部分(如匹配’magic’,而不是’magical’),

输出控制

-n:显示匹配行及行号。
-v:显示不包含匹配文本的所有行。
- -color=auto:可以将找到的关键词部分加上颜色的显示。
-h:查询多文件时不显示文件名。

-r:要求搜索子目录
-l:查询多文件时只输出包含匹配字符的文件名。
-L:查询多文件时只输出不包含匹配字符的文件名。
-c:只输出匹配行的计数。

上下文输出控制

grep -C number pattern files:匹配的上下文分别显示number行,此处C是大写的
grep -A number pattern files:匹配的文本所在行后面显示number行,after
grep -B number pattern files:匹配的文本所在行前面显示number行,before

pattern正则表达式主要参数:

\: 忽略正则表达式中特殊字符的原有含义。
^:匹配正则表达式的开始行。
$: 匹配正则表达式的结束行。
<:从匹配正则表达式的行开始。
\>:到匹配正则表达式的行结束。
[ ]:单个字符,如[A]即A符合要求 。
[ - ]:范围,如[A-Z],即A、B、C一直到Z都符合要求 。
.:所有的单个字符。
*:有字符,长度可以为0。

1
2
3
4
5
grep man *  # 会匹配 ‘Batman’、’manic’、’man’等,
grep '\<man' * # 匹配’manic’和’man’,但不是’Batman’,
grep '\<man\>' * # 只匹配’man’,而不是’Batman’或’manic’等其他的字符串。
grep pattern1 files | grep pattern2 # 显示既匹配 pattern1 又匹配 pattern2 的行。
grep "pattern1\|pattern2" files # 显示匹配 pattern1 或 pattern2 的行,须加反斜杠

实例

用于搜索某些文件夹下包含想要找的信息的文件,比如搜索当前文件夹下及子目录中包含mag字符的文件,并输出文件名

1
2
3
4
5
6
7
>>> grep -lr 'mag' *
bader_chg/OUTCAR
bader_chg/INCAR
bader_chg/OSZICAR
INCAR
OSZICAR
OUTCAR

sed命令

sed option ‘script’ file1 file2 …

option参数

-r 使用扩展正则表达式
-n 默认情况下,sed程序在所有的脚本指令执行完毕后,将自动打印模式空间中的内容,-n选项可以屏蔽自动打印。

脚本指令操作

a 追加append
i 插入insert
d 删除delete
s 替换substitution

1
2
3
sed "3a shell" file  # 在输出file内容的第二行后添加"shell"
sed '2,3d' file # 删除file中的第二、三行
sed 's/pattern1/pattern2/' file # 将file中匹配pattern1的字符串替换为pattern2
  • sed命令不会修改原文件,删除命令只表示某些行不打印输出,而不是从原文件中删去。
  • 使用查找替换命令时,可以把匹配pattern1的字符串复制到pattern2中。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    >>> cat testfile
    123
    abc
    456
    >>> sed 's/bc/-&-/' testfile
    123
    a-bc-
    456
    >>> sed -r 's/([0-9])([0-9])/-\1-~\2~/' testfile
    -1-~2~3
    abc
    -4-~5~6
    pattern2中的&表示原文件的当前行中与pattern1相匹配的字符串
    pattern2中的\1表示与pattern1的第一个()括号相匹配的内容,\2表示与pattern1的第二个()括号相匹配的内容。
  • 多条指令的话可以使用分号;隔开,也可以使用-e参数
    1
    2
    sed  's/pattern1/pattern2/;s/pattern3/pattern4/'  ./testfile
    sed -e 's/pattern1/pattern2/' -e 's/pattern3/pattern4/' testfile

另一种用法

/pattern/action
pattern为正则表达式,action为要执行的指令操作

1
2
3
4
-n /pattern/p  # 打印匹配pattern的行
/pattern/d # 删除匹配pattern的行
/pattern/s/pattern1/pattern2/ # 查找符合pattern的行,将该行第一个匹配pattern1的字符串替换为pattern2
/pattern/s/pattern1/pattern2/g #查找符合pattern的行,将该行所有匹配pattern1的字符串替换为pattern2

awk命令

非常强大的文本分析工具,列处理命令
awk [option] ‘script’ file
awk [option] -f scriptfile file

option参数

-F fs 或 –field-separator fs
指定输入文件折分隔符,fs是一个字符串或者是一个正则表达式,如-F:表示以冒号分割
-v var=value 或 –asign var=value
赋值一个用户定义变量。
-f scripfile 或 –file scriptfile
从脚本文件中读取awk命令。

基本用法

awk ‘{[pattern] action}’ filenames # 只能用单引号

1
2
3
4
5
6
7
awk '{print $1,$4}' test.log # 打印test.log文件中的第一列和第四列
awk '{printf "%-8s %10s\n",$1,$4}' test.log # 格式化打印,第一列占8个字符左对齐,第二列占10个字符右对齐
awk -F, '{print $1,$2}' test.log # 指定test.log中列分隔符为逗号,打印第一列和第二列
awk -F '[ ,]' '{print $1,$3}' test.log # 依次使用空格和逗号分割test.log中的列,
awk -v a=1 '{print $1,$1+a}' test.log # 设置变量a=1,打印第一列和第一列加1后的内容
awk -v a=2 '{print $1,$(1+a)}' test.log # 设置变量a=2,打印第一列和第三列
awk -v a=1 -v b=sh '{print $1+a,$1b}' test.log # 设置变量a=1,b=s,打印第2列和第一列加sh字符后的内容

高阶用法

awk_operator **awk 'BEGIN{ }{ }END{ }'** BEGIN{ }: 读入第一行文本之前执行的语句,一般用来初始化操作 { }: 逐行处理 END{ }: 处理完最后一行文本后执行,一般用来处理输出结果 awk可以引入各种条件、循环等,写出很复杂的用法。
1
2
3
4
5
6
awk '$1>2 && $2=="Are" {print $1,$2,$3}' test.log # 过滤第一列大于2并且第二列等于Are的行,并打印前三列
awk '$2 ~ /th/ {print $2,$4}' test.log # 输出第二列包含 "th",并打印第二列与第四列
awk '{print $1,$2,$5}' OFS=" $ " test.log # 以$分割输出的第一二五列
awk -F: 'BEGIN{i=0}{if($7~/bash$/){i++}}END{print i}' /etc/passwd
awk -F: 'BEGIN{i=0;j=0}{if($3<=500){i++}else{j++}}END{print i,j}' /etc/passwd
awk 'BEGIN{for(i=1;i<=5;i++){print i}}'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
>>> cat score.txt
Marry 2143 78 84 77
Jack 2321 66 78 45
Tom 2122 48 77 71
Mike 2537 87 97 95
Bob 2415 40 57 62
>>> cat cal.awk
#!/bin/awk -f
#运行前
BEGIN {
math = 0
english = 0
computer = 0

printf "NAME NO. MATH ENGLISH COMPUTER TOTAL\n"
printf "---------------------------------------------\n"
}
#运行中
{
math+=$3
english+=$4
computer+=$5
printf "%-6s %-6s %4d %8d %8d %8d\n", $1,$2,$3,$4,$5,$3+$4+$5
}
#运行后
END {
printf "---------------------------------------------\n"
printf " TOTAL:%10d %8d %8d \n", math, english, computer
printf "AVERAGE:%10.2f %8.2f %8.2f\n", math/NR, english/NR, computer/NR
}
>>> awk -f cal.awk score.txt
NAME NO. MATH ENGLISH COMPUTER TOTAL
---------------------------------------------
Marry 2143 78 84 77 239
Jack 2321 66 78 45 189
Tom 2122 48 77 71 196
Mike 2537 87 97 95 279
Bob 2415 40 57 62 159
---------------------------------------------
TOTAL: 319 393 350
AVERAGE: 63.80 78.60 70.00

shell之字符串常用操作

插入字符串

sed “ni$word” file
n表示在第几行行首插入,i表示插入操作,$word表示要插入的字符串(变量),file为待处理文本

替换字符串

替换字符

sed -n ‘s/string1/string2/g’ string
将string中所有匹配string1的字符串替换为string2

删除首字符

sed ‘s/^.//‘ string

删除指定行

sed ‘nd’ file
n表示第几行,d表示删除操作,不同行号用逗号隔开,不同命令用分号隔开,$可表示末行

1
sed '1,2d;$d' file  # 删除第一行第二行和最后一行

删除空行

sed ‘/^$/d’ file
符号^表示行首,$表示行尾;行首行尾间为空字符的情况即表示空行

提取字符串

按子串分割截取

**${varible#*string}** 从左往右,删除最短的一个以string结尾的子串,即截取第一个string子串之后的字符串

**${varible##*string}** 从左往右,删除最长的一个以string结尾的子串,即截取最后一个string子串之后的字符串

${varible%string}* 从右往左,删除最短的一个以string开头的子串,即截取最后一个string子串之前的字符串

${varible%%string}* 从右往左,删除最长的一个以string开头的子串,即截取第一个string子串之前的字符串

1
2
3
4
5
6
7
>>> test='./NNH/SideOn-out/opt'
>>> echo ${test%/*}
./NNH/SideOn-out
>>> echo ${test#*/}
NNH/SideOn-out/opt
>>> echo ${test:0:5}
./NNH

按位置及长度提取字符串

${varible:start:len}:截取变量varible从位置start开始长度为len的子串。第一个字符的位置为0。

awk ‘{print substr($1,1,2)}’ file
$1代表文件file的第一列,第二项1表示字符位置,第三项表示提取字符长度

单个指定字符串

两个指定字符串间的文本(单行)

sed -r ‘s/string1(.*)string2/\1/g’ string
\1 表示表示前面括号中匹配到的字符串

两个指定字符串间的文本(多行)

sed -n ‘/string1/,/string2/p’ file
//之间的字符为指定的字符串,前后用逗号隔开

文本输出

格式化输出

column -t 生成表格,将文本按列左对齐
column -t -R1,2,3 生成表格,将文本按列左对齐,其中1,2,3列右对齐
awk ‘{printf “%16s%.6f\n”,$1,$2}’ file 将file文件中第1列占据16个字符右对齐,第2列按浮点数打印,并打印到小数点后六位

echo 打印输出

echo string # 普通字符串双引号可加可不加

1
2
3
4
5
6
7
8
>>> echo "OK!";echo "It is a test"
OK!
It is a test
>>> name=Tom;echo -e "OK! \c";echo "$name is coming." # -e 开启转义, \c 不换行
OK! Tom is coming.
>>> echo "It is a test" > myfile # 重定向至文件myfile
>>> echo '$name\"' # 用单引号,原样输出字符串,不转义或取变量
>>> echo `date` # 反引号,显示命令执行结果

printf打印输出

printf format-string [arguments…] # format-string: 为格式控制字符串; arguments: 为参数列表。

1
2
3
4
>>> printf "Hello, Tom\n"
Hello, Tom
>>> printf "%-10s %-8s %-4.2f\n" 郭靖 男 66.1234
郭靖 男 66.12

%s %c %d %f都是格式替代符;
%-10s 指宽度为10个字符(-表示左对齐,没有则表示右对齐),任何字符都会被显示在10个字符宽的字符内,如果不足则自动以空格填充,超过也会将内容全部显示出来。
%-4.2f 指格式化为小数,其中.2指保留2位小数。

数组

Shell只支持一维数组,数组用括号来表示,元素用”空格”符号分割开,初始化时不需要定义数组大小,数组元素的下标由0开始:
array_name=(value1 value2 … valuen)

1
2
3
4
5
6
7
>>> atoms=() # 定义空数组
>>> atoms=(Pt1 O1 O2) #
>>> ${atoms[0]} # 调用数组中第一个元素Pt1
>>> ${atoms[3]} = N1 # 给数组添加元素N1
>>> ${my_array[@]} # 使用@ 或 * 可以获取数组中的所有元素
>>> ${#my_array[@]} # 获取数组长度,与获取字符串长度的方法相同,加#号
>>> ${!names[@]} # 获取数组索引指数,for i in ${!names[@]}

数学运算

运算执行命令

a. 用let指示数学运算,变量计算中不需要加上 $ 来表示变量。注意运算符号前后不能有空格!如果表达式中包含了空格或其他特殊字符,则必须用单(双)引号引起来。

1
2
3
4
>>> let b=1+2;echo $b
3
>>> b=1+2;echo $b
1+2

b. 用$[]表示数学运算。表达式在中括号内。有无空格都可以。例如命令echo $[1+2],将输出结果3。

c. 用expr改变运算顺序。注意运算符号需要用空格隔开!

1
2
3
4
>>> expr 1 + 2
3
>>> expr 1+2
1+2

d. 用bc表示数学运算。可以结合echo命令一起使用。若表达式有空格时须加引号。
e. 使用awk表示数学命令。
注意:只有bc和awk可做浮点运算!优先选用awk。也可将运算结果赋值给新的变量。

1
2
3
4
5
6
7
8
>>> echo 2 | awk '{printf "%.3f\n", $1**0.5}'
1.414
>>> a=1.2;b=2.1;awk -v a=$a -v b=$b 'BEGIN{print a+b}'
3.3
>>> a=1.2;b=2.1;echo "$a+$b" | bc
3.3
>>> a=1.2;b=2.1;c=$(echo "$a+$b" | bc);echo $c
3.3

运算符

算数运算符

+ - * / % 加 减 乘 除 取余
== 用于比较两个数字,相同则返回true
!= 用于比较两个数字,不相同则返回true
注意:条件表达式要放在方括号之间,并且要有空格,例如: [$a==$b] 是错误的,必须写成 [ $a == $b ]。
使用expr运算时,乘号(*)前边必须加反斜杠(\)才能实现乘法运算;

关系运算符

关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
-eq 检测两个数是否相等,相等返回 true。
-ne 检测两个数是否不相等,不相等返回 true。
-gt 检测左边的数是否大于右边的,如果是,则返回 true。
-lt 检测左边的数是否小于右边的,如果是,则返回 true。
-ge 检测左边的数是否大于等于右边的,如果是,则返回 true。
-le 检测左边的数是否小于等于右边的,如果是,则返回 true。

逻辑运算符

! 非运算,表达式为 true 则返回 false,否则返回 true。
&& 逻辑的 AND ,或着用 -a
|| 逻辑的 OR ,或者用 -o

字符串运算符

以a=”abc”, b=”def”为例
= 检测两个字符串是否相等,相等返回 true。 [ $a = $b ] 返回 false。
!= 检测两个字符串是否相等,不相等返回 true。 [ $a != $b ] 返回 true。
-z 检测字符串长度是否为0,为0返回 true。 [ -z $a ] 返回 false。
-n 检测字符串长度是否为0,不为0返回 true。 [ -n “$a” ] 返回 true。
$ 检测字符串是否为空,不为空返回 true。 [ $a ] 返回 true。

文件测试运算符

文件测试运算符用于检测 Unix 文件的各种属性。
-d file 检测文件是否是目录,如果是,则返回 true。 [ -d $file ] 返回 false。
-f file 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。 [ -f $file ] 返回 true。
-r file 检测文件是否可读,如果是,则返回 true。 [ -r $file ] 返回 true。
-w file 检测文件是否可写,如果是,则返回 true。 [ -w $file ] 返回 true。
-x file 检测文件是否可执行,如果是,则返回 true。 [ -x $file ] 返回 true。
-s file 检测文件是否为空(文件大小是否大于0),不为空返回 true。 [ -s $file ] 返回 true。
-e file 检测文件(包括目录)是否存在,如果是,则返回 true。 [ -e $file ] 返回 true。

shell流程控制

管道|

条件控制

if

1
2
3
4
5
6
7
if condition
then
command1
command2
fi

if [ condition ]; then command1; fi # 单行写法,常用于awk中或终端命令提示符

if else

1
2
3
4
5
6
7
if condition
then
command1
command2
else
command3
fi

if elif else

1
2
3
4
5
6
7
8
9
if condition1
then
command1
elif condition2
then
command2
else
commandN
fi

for循环

1
2
3
4
5
6
7
for var in item1 item2 ... itemN
do
command1
command2
done

for var in item1 item2 ... itemN; do command1; command2… done;

while循环

while循环用于不断执行一系列命令,也用于从输入文件中读取数据;命令通常为测试条件。
跳出循环,使用break跳出所有循环和continue跳出当前循环。

1
2
3
4
while condition
do
command1
done
1
2
3
4
5
6
7
# while循环可用于读取键盘信息。输入信息被设置为变量FILM,按<Ctrl-D>结束循环。
echo '按下 <CTRL-D> 退出'
echo -n '输入你最喜欢的网站名: '
while read MUSIC
do
echo "是的!$MUSIC 是一个好网站"
done
1
2
3
4
5
6
7
8
9
10
11
12
# 无限循环
while true
do
command
done

while :
do
command
done

for (( ; ; ))

case多选择语句

多分枝选择结构,每个case分支用右圆括号开始,用两个分号;;表示break,即执行结束,跳出整个case … esac语句,esac作为结束标记。
取值后面必须为单词in。取值可以为变量或常数。匹配发现取值符合某一模式后,其间所有命令开始执行直至 ;;。如果无一匹配模式,使用星号*捕获该值,再执行后面的命令。模式支持正则表达式。

1
2
3
4
5
6
7
8
case values in
value1)
command1
;;
value2)
command1
;;
esac
1
2
3
4
5
6
7
8
9
10
11
12
13
echo '输入 1 到 3 之间的数字:'
echo '你输入的数字为:'
read aNum
case $aNum in
1) echo '你选择了 1'
;;
2) echo '你选择了 2'
;;
3) echo '你选择了 3'
;;
*) echo '你没有输入 1 到 3 之间的数字'
;;
esac

shell函数

其他

外部脚本引用

1
2
source ./test.sh
. ./test.sh

输入输出重定向