shell编程基本技巧与语法总结

特殊字符

‘#’井号

1
2
3
4
#comment 注释
echo "hello"#comment #注意,#和命令不能紧挨着
result=$(( 2#101011 )) #不是注释,是数制转换
特定的模式匹配也可能用到#

‘;’命令分隔符,可以在一行写多个命令

1
2
3
echo "hello" ; echo "world"
if test $name == 'perkyoung'; then
fi

‘;;’,有时候’;’是需要转义的

1
2
3
4
5
case name in
perkyoung)
echo "...."
;;
esac

‘.’,

1
2
3
4
类似source命令
如果一个文件开头是'.'字符,这个文件是隐藏文件
如果是目录,单独的'.'代表当前目录
正则表达式中,'.'匹配任何单独的字符

‘,’

1
2
3
let "result=((a=10,15/3))"  #链接一系列算数操作,虽然都执行了,但是只有最后一个被返回
echo $a #9
echo $result #5

‘`’

1
result=`commands`   #命令替换,可以将命令的输出赋值给一个变量。关注后面的后置引用,后置标记

:

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
: #空命令,什么也不敢,也可以理解为和shell的true作用相同。是bash的内建命令,退出码是0
echo $?

下面是死循环
while :
do
.....
done

下面和上面相同
while true
do
...
done

在if/then中作为占位符
if conditon
then :
fi

清空一个文件
: > ./text.txt #相当于 cat /dev/null > ./text.txt,但是这并不会产生一个新的进程,因为:是内建命令

/etc/passwd, PATH的分隔符
yangpengfei:x:1000:1000:yangpengfei,,,:/home/yangpengfei:/bin/bash

‘!’

1
2
if grep perkyoung /etc/passwd   #取反命令的退出码
if $a != 10

‘*’

1
2
3
4
ls *    #文件的通配符
\d* #正则表达式中,这个表示任何个数字,包括0个
* #乘法
** #幂运算

‘$’

1
2
3
4
5
6
7
8
echo $result    #引用变量
$? #返回码
${} #参数替换
$*, $@ #位置参数, $* 所有的参数作为一个字符串,$@是每个参数作为一个独立的单词,for i in $@ ;do ....done,使用的时候都被引用起来,只有这样,她俩才会不同
shift #去掉$1,$@指向剩余的参数
$! #运行在后台的最后一个作业的pid,
$_ #保存着之前命令的最后一个参数值
$$ #当前进程的pid,一般用来构造唯一临时文件名。

‘()’命令组

1
2
(a=10; echo $a) 命令组,将括号内的命令列表,作为一个子shell来运行,所以内部的变量更像是一个内部变量,外部的父shell是无法获取里面的变量的
Array=(1 3 4 5) #初始化数组

‘{xxx,yyy,zzz}’大括号扩展

1
2
cat {file1,file2,file3} > file  #将三个文件联合起来写到file中,注意,这三个文件中间不能有空格
cp file.{txt,backup} #拷贝file.txt,file.backup

‘{}’

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
内部组,其实是创建了一个匿名函数,但是里面的变量对外部还是可见的。与()不同的是,大括号不会开启一个新的shell
a=123
{ a=321 }
echo "a=$a" # a=321

#代码块与文件重定向
#!/bin/bash
FILE=./tmp
{
read line1
read line2
} < $FILE
echo line1
echo line2

#将一个代码块结果重定向到文件
#!/bin/bash
{
echo ""
commands
} > file.txt

‘&’ 后台运行一个命令

1
2
ls -l & #有时候在后台运行一个命令,整个脚本就挂起了,解决方案是在接下来一行添加wait
wait

‘(())’

1
2
整数扩展
扩展并计算括号内的表达式

‘> &> >& >> < <>’

1
2
3
4
command >filename   #重定向到filename
command &>filename #重定向stdout,stderr到filename
command >&2 #重定向stdout到stderr
[i]<>filename #打开filename,读写,如果没有则创建,分配文件描述符i

‘[]’

1
2
3
4
5
正则,索引
if [[ $a > $b ]]
then
commands
fi

‘<,\>’

1
grep '\<perkyoung\>' ./test #单词的边界

‘-‘

1
2
3
用于重定向stdin或stdout
(cd /source/directory && tar cf - . ) | (cd /dest/directory && tar xpvf -)
等同于 cp -a /source/directory/* /dest/directory

变量和参数的介绍

变量替换

引用变量的值,叫做变量替换 $

1
2
3
4
result=23   #变量不带$,一般是变量被声明,被赋值,unset,或者export
echo $result #这个这是引用的简写形式,如果遇到什么问题,就需要用${result}了
echo "$result" #如果是双引号,则变量的替换是不能被阻止的,又成为部分引用或弱引用
echo '$result' #如果是单引号,则变量的替换完全被阻止,成为全引用或者部分引用

举例,赋值和替换

1
2
3
4
5
6
7
a=332
hello=$a
variable=value #一定不能有空格,如果是variable =value,将被解释为,variable是一个命令,后面是参数。如果是variable= value将被解释为,执行了一个value命令,并且添加了一个环境变量,该变量的值为空。

hello="a b c d" #有空格的时候一定要加引号
echo $hello #a b c d
echo "$hello" #a b c d , 输出结果不同,所以如果是引用变量,则保留空白,如果是变量替换,则不保留空白

变量的赋值 =

1
2
3
4
5
let a=16+5  #一般最好加上双引号吧,因为如果是 a +=1,这种,不加引号是不行的
for循环赋值
read赋值
a=`ls -l` 命令赋值,注意在引用a的时候要加上"$a",这样里面的tab都会引用到
a=$(ls -l) 这个和上面的几乎一样。只是一种更新的方法

bash变量是不区分类型的

bash是不区分类型的,所有的都是字符串,当然也进行比较操作和整数操作,关键因素是里面是不是全是数字

1
2
result=1234
tmp=${result/23/bb} #将23替换为bb

特殊的变量类型

环境变量
如果一个脚本要设置一个环境变量, 那么需要将这些变量”export”出来, 也就是需要通知到脚本
本地的环境. 这是export命令的功能.

位置参数

1
2
3
4
5
6
$1,$2,$3......${10},${11}   #超过9之后,就需要用大括号了

args=$# #参数个数
lastargs=${!args} #如果想取得最后一个参数,如果$4代表最后一个,那如何引用这个4呢,就用到了间接引用或者用${!#},注意不是${!$#}

shift #除$0外,将所有参数左移,

引用

$
表示不取$后面的变量的值
abc=$’\101\102\103\104’ 八进制数编码

退出码

任何一个函数,或者进程都会返回一个状态吗,例如exit 0,状态吗0-255,如果没有这个exit指令,则退出码是最后一次执行的指令的退出码

条件判断

条件测试结构

if/then来判断命令列表的退出码是否为0,在unix中0代表成功,如果成功的话,则执行接下来的指令

[]

1
2
if [ $a -eq $b ]    if test $a -eq $b
终于明白了,这个是内建命令,和test功能一致,测试内部的比较表达式,根据比较的结果返回一个退出码,0表示真,1表示假

[[]] 扩展测试命令,比[]更通用,&&,||,<,>可以非常正常得出现在其中,不会出现逻辑错误,test也无法胜任这种情况。

1
if [[ $a == $b ]]

((…)) let

1
2
3
let "1<2" return 0
(( 0 && 1 )) #查看算数表达式的结果,如果为非0,则返回退出码0,表示正确
(( 0 )) #这个值为0,所以返回错误,也就是返回值为非0的值

if能测试任何命令,不仅仅是中括号中的条件

1
2
3
4
5
6
7
8
9
if grep -q "perkyoung" /etc/passwd  这个太赞了。
if echo "hello" | grep -q "./file"

a="./aa"
b="./bb"
if cmp $a $b &> /dev/null
then

反过来,[]也不一定非要用if,[ $a -eq $b ] && commands #这样也行

文件测试操作符

1
2
3
4
5
6
7
8
9
10
11
12
13
-e  文件存在
-s 文件大小不为0
-h 这是一个符号链接
-L 同上
-S socket
-t 被关联到一个终端设备上
-g sgid是否被标记在文件上
-u suid是否被标记在文件上
-O 是否是文件的拥有者
-N 从文件上一次被读到现在是否被修改过
f1 -nt f2 f1比f2更新
f1 -ot f2 f1比f2更旧
f1 -et f2 是相同的硬链接

操作符

算数操作符

1
2
3
4
5
(( n=n+1 ))
n=$((n+1))
let "n++"
let "a+=1"
((n++))

位操作符

1
2
3
4
5
6
7
8
9
<<  左移一位
let "value<<=2" 左移两位,并赋值
& 按位与
&=
| 按位或
~ 按位反
! 按位非
^ 按位异或XOR
^=

逻辑操作符

数字常量

1
2
3
let "t=032" #八进制
let "t=0x11" #16进制
let "t=2#11" #用11表示一个2进制的书,base#number

变量重游

内部变量

内容不少,以后可以当做手册来看

操作字符串

字符串长度

1
2
${#string}
len=`expr length $string`

匹配字符串开头的子串长度

1
2
3
4
expr match "$string" '$substring'   #$substring是一个正则表达式
或 expr "$string" : '$substring'
string="abcdABCD123abcABC"
len=`expr match "$string" 'abcd[A-Z]*'` #8从头开始匹配了8个字符

索引

1
2
3
4
expr index $string $substring
在string中匹配到的第一次出现substring的位置
string="abcdABCD123abcABC"
echo `expr index "$string" '[A-Z]*'` #结果为5

提取子串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
${string:position}  #从position位置开始提取子串,从0开始计数
${string:position:length} #

string="abcdABCD123abcABC"
echo ${string:3:3} #dAB
echo ${string: -4} ; echo ${string:(-4)}; echo ${string:(-4):3} #从右边截取,截取后四个,注意要有空格或者括号

echo ${*:2} #如果不提供变量,则截取的就是命令行的参数,这个是从第二个开始到最后面的参数
echo ${@:2}
echo ${*:2:3}

expr substr $string $position $length

expr match "$string" '\($substring\)' #从开头位置匹配substring,substring是一个正则表达式
或者expr "$string" : '\($substring\)' #同上

子串消除

1
2
3
4
5
6
7
8
9
${string#substring} #从开头位置节点最短匹配的$substring,这里也是开头位置
${string##substring} #从开头位置节点最长匹配的$substring,这里也是开头位置

string="abcdABCD123abcABC"
echo ${string#a*c} #dABCD123abcABC
echo ${string##a*c} #ABC

${string%substring} #从结尾,截取最短
${string%%substring}#同上,截取最长

子串替换

1
2
3
4
${string/$substring/$replacement} #使用replacement来替换第一个匹配的substring
${string//$substring/$replacement} #使用replacement来替换所有匹配的substring
${string/#$substring/$replacement} #如果substring匹配开头的字符,使用replacement来替换开头的串
${string/$$substring/$replacement} #如果substring匹配结尾的字符,使用replacement来替换结尾的串

参数替换

1
2
3
4
5
6
7
8
9
10
11
12
13
${parameter}    #可以和字符串组合起来实用
${param=default} #如果变量没有被声明,则值为default
${param:=default} #如果变量没有被设置,则值为default
echo "username=${username=`whoami`}
username=
echo "username=${username:=`whoami`}

${param+alt_value} #如果变量已经被声明,那么值就是后者,否则为null
${param:+alt_value} #如果变量已经被设置,则值为后者,否则为null

${param?err_msg} #如果变量被声明,就使用设置的值,否则打印err_msg
${parma:?err_msg} #如果变量被设置,就使用设置的值,否则打印err_msg
: ${HOSTNAME?} ${USER?}

指定变量类型

1
2
3
4
5
6
7
declare -r 创建只读变量
-i 创建int类型
-a 数组
-f 函数
-x export变量
-x val=$value export变量
并且declare可以限制变量的作用域,比如函数内部declare了变量VA,那外部是无法引用该变量的。非declare的变量,在外部是可以的使用的

变量的间接引用

一个变量的值是另一个变量的名字

1
2
3
4
5
a=letter_of
letter_of=z
如何通过变量a来获取到z呢?
eval a=\$$a #a=z
或者 a=${!a}

$RANDOM产生随机整数

$RANDOM不是常量,是内置函数

双圆括号结构

与let很相似,允许算数扩展和赋值,可以认为是bash使用C语言风格的处理机制

1
2
3
4
5
6
7
#/bin/bash
(( .... ))
(( a = 23 )
(( a++ ))
(( a-- ))
(( ++a ))
(( t = a<45?7:11 ))

循环与分支

循环

for循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
for arg in [list]
do
done

for arg in "" "" "" #多个参数
for arg in "a b c ddfa" #四个参数
for planet in "Mercury 36" "Venus 67" "Earth 93" "Mars 142"
do
echo $planet
done

可以使用通配符
for file in *
for file in [jx]*

下面将直接遍历命令行参数
for arg

可以使用命令来产生list
for arg in command
for word in $(strings "$2" | grep "$1")

还可以是C语言风格
for (( a=1,b=1; a<= LIMIT; a++,b++ )) #注意,都没有$符号

while,更适合在循环次数未知的情况下使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
while [condition]
do
commands
done

var0=0
LIMIT=10

while [ "$var0" -lt "$LIMIT" ]
do
echo -n "$var0 "
$var0=`expr $var0 + 1`
done
echo
exit 0

一个while循环可以有多个判断条件,最后一个条件决定是否退出循环

同样支持C风格
while (( a <= LIMIT )) 没有$

until
如果条件一直为false,则继续

1
2
3
4
until [ condition-is-true ]
do
commands
done

测试与分支

1
2
3
4
5
6
7
8
9
10
11
case "$person" in
"E" | "e" )
commands
;;
[0-9] )
commands
;;
-c | --conf )
commands
;;
esac

select

1
2
3
4
5
6
7
8
9
PS3='choose your favorite vegetable: '  #select用的命令提示符是PS3
echo
select var in "beans" "carrots" "potatoes" "onions"
do
echo
echo "Your favorite veggie is $var"
echo
break #没有break会一直执行下去,
done

内部命令和内建命令

内建命令比外部命令更快,一般情况下,外部命令都需要单独fork出一个进程去执行,内建命令一般要访问内核.
bash重新实现了很多命令作为内建命令,比如echo

1
2
3
4
echo
printf format-string parameter
printf "PI is %1.2f\n" 3.1415926
pi=$(printf "PI is %1.2f\n" 3.1415926)

read,从stdin读取一个变量的值,-a可以read数组变量

1
2
3
4
5
6
7
8
9
10
11
12
13
read num
read num_1 num_2 #如果值输入了一个值,则另一个值是未设置状态
read 如果不加参数,则把值存到了专用变量$REPLY上面

多行输入,就在每行结尾假如'\',这样多行就会变为1行为这个变量赋值,如果想读入'\',就read -r var

read val_1 val_2 < ./tmp
while read line
do
done < ./tmp

管道
find . -print | while read f ......

pushd, popd, dirs

1
2


变量let
被看做是expr的一个简化版本

1
2
3
let a=11
let a=a+5
let "a -=5"

set

1
2
3
4
5
6
set vale=10
set `uanme -a` #用这个命令的结果重新设置位置参数的值

variable="one two three...."
set -- $variable #将位置参数设置为$variable
set -- #将位置参数unset

unset 删除一个shell变量,效果是置为null,但是对位置参数无效

1
2
unset variable
unset PATH

getopts 这个以后再看

1
2


source, . 效果和include类似,最终的结果是在代码中插入了一段代码

exec,使用一个特定的命令来取代当前进程,一旦运行结束,则会自动结束该脚本,一般情况下,遇到一个命令,shell会fork一个 进程,但是这个不会fork,而是直接替换

1
2
3
4
exec echo "jdofaj"
echo "dofa" #不会执行这里,

别忘了,还可以重新分配文件描述符 exec 7<./file

caller 放到函数中,会显示调用者信息
true 返回成功退出码
false 返回失败退出码
type 和which很像,鉴别是关键字还是内建命令

作业控制命令

fg,bg,kill,killall,command,builtin,enable,autoload

外部过滤器,程序和命令

基本命令

1
2
3
4
5
6
cat/tac/rev 连接文件/行反转/内容反转
chattr 修改文件属性,但是这个命令只运行在ext2中,比较有用的是chattr +i file,这个文件永远不变,包括root的所有操作,除非root先更改其属性

ln -s 创建符号链接,即软链接,可以跨越文件系统
ln -s oldfile newfile
ln oldfile newfile 软硬链接,在该内容的时候,打开任何一个文件,都是改过的。但是如果删除或者重命名原文件,那么软链接讲找不到该文件,硬链接依然不受影响,软链接有一个很大的好处,就是跨文件系统

复杂命令

find

1
2
3
4
find . -name "*.txt"
find . -name "*.txt" -exec rm {} \; #要对文件执行操作就需要加-exec,后面跟命令,find出来的文件就会替换掉{}, 最后的‘;’是转义字符,保证传递给shell的字符,不会被转移成其他字符
find ~/ -mtime 1 #列出最后一天修改的文件,atime是最后的访问时间,ctime是文件状态更改的时间
find ~/ -type f -atime 3 -exec rm {} \; #删除最近三天没有被访问过的文件

xargs
给命令传递参数的过滤器,也是组合多个命令的工具,传过来的换行和空白,都将被空格取代,如果在管道中传参遇到问题,可以试试这个,一般都能成功。

1
2
ls . | xargs -I '{}' -t cp ./{} $pwd    指定替换{}
-n 指定每个参数的字符宽度

expr
通用求值表达式,

1
2
3
4
5
expr 3 + 5
expr 5 % 3
expr $y + 1
y=`expr $y + 1`
z=`expr substr $string $postion $length`

时间/日期命令

1
2
3
4
5
6
7
8
9
date
time
touch
at
batch
cal
sleep
usleep
clock

文本处理命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
sort
tsort
uniq 删除已经排序的重复航 ,-c 统计每行的重复此时 uniq -c | sort -nr
expand 把每一个tab转化成空格
unexpand 与上相反
cut 从文件中提取特定域
paste 以列的形式合并文件
join 类似paste,但是他可以将具有特定标记域的行合并起来,例如
cat 1.data
100 shoes
200 socks

cat 2.data
200 $10.00
100 $20.00

join ./1.data ./2.data
100 shoes $40.00
200 socks $20.00

head -c 字节,-n行数
tail -f 动态查看末行

grep pattern file

1
2
3
4
grep
egrep "desk|document" ./test #相当于grep -E,支持正则表达式
fgrep 相当于grep -F
look 可以当做查单词的利器,默认从/usr/share/dict/words中查找

其他命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
wc,sed,awk
tr 字符转换过滤器
tr a-z A-Z #转换大小写
tr -d 0-9 #删除所有数字
tr -d '\015' < file > outfile #转dos到Unix,去掉了回车

fold 按照指定的宽度进行折行
fmt 也是折行工具

col 这个命令用来滤除标准输入的反向换行符号. 这个工具还可以将空白用等价的tab来替换
column 在合适的位置插入tab
nl 将内容输出倒stdout,并且在非空行加入行号,和cat -n类似,不过后者空行也会有行号

pr 格式化过滤器,可以对文件进行分页,输出更美观

iconv 可以将文件转化为不同编码格式 ,iconv -f utf-8 -t utf-16

通讯命令

1
2
3
4
5
6
host
dig
traceroute
ping
whois
rsync ##需要认真学习一下

数学命令

终端命令

混杂命令

1
2
3
jot,seq #生成一系列的整数,常用于for循环中
dd ##这个命令也要好好学习一下
od

系统管理

1
2
3
4
5
users
groups
chown
chgrp
useradd, userdel