转载地址:https://laoyur.com/?p=638
本文目的:bash速查,快速上手。
目录
1 |
bash xxx.sh #执行xxx.sh文件 |
2 |
bash -c command #以字符串方式直接执行。-c 的c代表command。如果command有参数(即有空格),需要用引号包裹。 |
可以在执行脚本前预先设置变量:
1 |
VAR1= "hehe" VAR2= "heihei" bash -c 'echo $VAR1; echo $VAR2' |
变量VAR1和VAR2的作用域只在该条语句中有效,不会污染当前shell。
另外,上面的这个例子中,单引号不能换成双引号,否则无法输出hehe、heihei,原因是啥?请继续看下文。
单引号中的字符串不进行转义,原样输出;双引号中的内容会被转义。
例:echo ‘$HELLO’,输出 $HELLO。echo “$HELLO”,输出 world(假设$HELLO=world)。
回到上面那个例子,单引号使得$VAR1 和 $VAR2 原样传给bash -c 命令,从而获得了正确结果。
如果单引号换成双引号,则意思是输出当前shell中的$VAR1、$VAR2变量的值。而当前shell中有没有值是无可预料的,也就无法输出hehe、heihei了(再次提醒,VAR1=”hehe” VAR2=”heihei”的作用域不在当前shell中)。
一般规范的shell脚本,首行都是:
1 |
#!/bin/bash |
如果你的默认shell就是bash,或者你使用 bash xxx.sh方式打开的话,这行可以省略。
但如果别人来执行你的脚本,或者默认shell不是bash呢?
为了确保你的代码始终是用bash执行(通常你仅会为一种shell进行代码测试),加上这行很有必要。
额外的,注意一下 /etc/rc.local 文件的首行,你会发现长这样:
1 |
#!/bin/sh -e |
多了个 -e 参数,表示一旦某条命令执行出错(返回值不为0),立即退出该脚本。
也可以使用 set -e 来达到同样的目的。下文会有关于 set 的更多详情介绍。
通常一行只写一个语句,此时分号可以省略。但如果需要在一行中写多个语句,分号必须写。
例:
1 |
if [ 2 -eq 2 ]; then echo =; else echo !=; fi |
使用井号 #
使用冒号 :
使用declare(或typeset,两者完全等价。同时typeset也被zsh所支持)进行变量声明:
1 |
declare STRING_VAL #声明一个字符串变量 |
2 |
declare -r READONLY_VAL #声明只读变量,也可以写作 readonly READONLY_VAL |
3 |
declare -i INTEGER1 #声明整型变量 |
4 |
declare -a ARRAY1 #声明数组变量 |
5 |
declare -f #打印出所有function |
6 |
declare -f FUNC_NAME #打印出指定名称的function |
7 |
declare -x VAR_TO_EXPORT #声明导出变量。跟使用export命令效果一样。 |
8 |
declare -i -r ABC=2 #在声明变量类型的时候可以直接赋值 |
readonly的变量如果被导出到子shell,子shell拿到的拷贝将会失去readonly属性。
例:FILE_NAME=’abc.txt’
变量名不能使用保留字符,等号两侧不能有空格(否则会被认为是一个shell命令,而非赋值)。
被赋值的变量可以从未被声明。从未被声明过的变量默认是字符串类型。
1 |
declare A=123 # 声明一个字符串变量A,并赋值为"123" |
用字符串对一个整型变量进行赋值时,不会报错,但结果却是不可预料的。也就是说赋值操作不会改变变量被声明时指定的类型。例:
1 |
declare -i NUMBER |
2 |
NUMBER=abc |
3 |
echo $NUMBER #输出 0 |
没有用declare -i 声明为整形的变量,统统是字符串变量。
当然,即便是字符串变量,在整数运算时还是会被解释成整数。
当省略declare时,不管位于函数外还是函数内,变量的作用域为程序结束;
当在函数内部使用declare时,作用域为函数结束;
当在函数外部使用declare时,作用域为程序结束。
另外,在函数内,declare还有个别名,叫local,用这货能减少歧义。
所以,local也能使用declare的参数。
例:
1 |
function func { |
2 |
local -i HELLO=123 |
3 |
HELLO+=1 |
4 |
echo $HELLO #输出124 |
5 |
} |
使用$符号加变量名进行变量引用。
例:FILE_NAME1=$FILE_NAME
标准的写法是${FILE_NAME},简写时可以省略花括号,但某些会引起歧义的地方,还是得用标准写法。比如引用数组变量(下文对数组有详述)。
$HOME:当前用户的根目录路径
$PATH:PATH环境变量
$PWD:当前工作路径
$0,$1,$2,…:第0个参数(shell脚本自身),第1个参数……
$#:参数的个数(不包括$0)
$@:获取本脚本程序/函数所有传入参数。
$*:同$@。但用双引号包裹后意义不同,$@、”$@”、$* 展开为原始参数,”$*”展开为一个字符串,内容为原始参数。
以下为示例代码:
01 |
function showArgsCount { |
02 |
03 |
echo "args count:$#" |
04 |
05 |
} |
06 |
07 |
function passArgs { |
08 |
09 |
showArgsCount "$*\n" |
10 |
11 |
} |
12 |
13 |
function passArgs2 { |
14 |
15 |
showArgsCount "$@\n" |
16 |
17 |
} |
18 |
19 |
20 |
passArgs a b c |
21 |
passArgs2 a b c |
输出结果为:
1 |
args count:1 |
2 |
args count:3 |
$?:获取最近一次执行的脚本、程序或函数的返回值。正常为0,非0为错误。
$$:该脚本程序的进程号
$RANDOM:1-65536之间的整数
详情请点击:http://tldp.org/LDP/abs/html/string-manipulation.html
这里只记录几个常用的用法。
1 |
STR=abcdefg |
2 |
POS=2 |
3 |
LEN=3 |
4 |
echo ${ #STR} # 输出7,$STR变量的strlen(); |
5 |
echo ${STR:2} # 输出cdefg,从position 2处开始取substring |
6 |
echo ${STR:2:3} # 输出cde,从position 2处开始取长度为3的substring |
7 |
echo ${STR:$POS:$LEN} #输出cde,position和length都可以是变量 |
8 |
echo ${@:2} #从此shell脚本传入args中取substring,@可以换成* |
${string#substring}
从string中删除一个最短匹配,从开头往后匹配。#换成%,表示从结尾往前匹配。
${string##substring}
从string中删除一个最长匹配,从开头往后匹配。##换成%%,表示从结尾往前匹配。
例:
01 |
stringZ=abcABC123ABCabc |
02 |
# |----| shortest |
03 |
# |----------| longest |
04 |
05 |
echo ${stringZ #a*C} # 123ABCabc |
06 |
# Strip out shortest match between 'a' and 'C'. |
07 |
08 |
echo ${stringZ ##a*C} # abc |
09 |
# Strip out longest match between 'a' and 'C'. |
10 |
11 |
12 |
13 |
# You can parameterize the substrings. |
14 |
15 |
X= 'a*C' |
16 |
17 |
echo ${stringZ #$X} # 123ABCabc |
18 |
echo ${stringZ ##$X} # abc |
19 |
# As above. |
${string/substring/replacement}
将string中的第一个substring替换为replacement
${string//substring/replacement}
将string中的所有substring替换为replacement
${string/#substring/replacement}
如果string以substring开头,则把substring替换为replacement
${string/%substring/replacement}
如果string以substring结尾,则把substring替换为replacement
首先可以使用let或expr进行整数运算,运算符支持 + – * / %
例:
1 |
NUM=12 |
2 |
let NUM=$NUM+1 |
3 |
echo $NUM |
输出13。或者用expr来做:
1 |
NUM=12 |
2 |
NUM=` expr $NUM + 1` |
3 |
echo $NUM |
两者等价。需要注意的是,let方式+两侧不能留空格,要留空格的话,需要把整段用引号包裹;而expr方式中,+号左右必须要留空格。所以结论是:let和expr都挺难用的。不过别急,还有另一种方式:
(( … )) 方式。例:
1 |
a=1 |
2 |
b=2 |
3 |
(( c=a+b )) |
4 |
echo $c |
结果会输出3。
(( ))中的操作都会被处理成整数运算,由于不会被解释成字符串,因此引号和变量前的$符号都是不强制的。
此外,还支持 +=、-=、*=、/=、%=、>、< 等操作符,操作符左右的空格也是想加就加,怎么样,跟高级语言一样爽歪歪吧。
另外值得一提的是,$(( … ))才是这货的标准形式(POSIX标准)。
bash不支持浮点运算,如果上面的let例子中,NUM=12.1,那+1运算就会报错。实在想处理浮点数的话,需要借助外部命令:dc、awk或者bc。
猛击此链接查看更多关于整数运算的资料:
http://mywiki.wooledge.org/ArithmeticExpression
先讲最基本的if – fi 语句。基本语法:
1 |
if condition; then |
2 |
echo hehe |
3 |
echo haha |
4 |
else |
5 |
echo haha |
6 |
echo hehe |
7 |
fi |
1 |
if condition; then |
2 |
: |
3 |
elif condition; then |
4 |
: |
5 |
else |
6 |
: |
7 |
fi |
如果then/else分支不需要写代码,则用冒号 : 来占位
condition表示一个判断语句。
然后讲讲case – esac(很明显,case倒着写就是esac)语句。
这货类似于switch case。
基本语法:
1 |
case $val in |
2 |
val1) command ;; #分支1 |
3 |
val2 | val3) command ;; #分支2或3 |
4 |
*) command ;; #default |
5 |
esac |
判断语句的返回值是true or false。
字符串判断:
1 |
[ $VAR1 = $VAR2 ] #两个字符串是否相等。另外,!= 表示不相等,> < 分别表示 大于小于。 |
2 |
[ -z $VAR ] #是否为空串(未定义的变量也属于空串) |
3 |
[ $VAR ] #是否不为空串 |
文件判断:
1 |
[ -e $FILE ] #文件是否存在 |
2 |
[ -f $PATH ] #是否是普通文件(非dir或symbol link之类) |
3 |
[ -d $PATH ] #是否是目录 |
4 |
[ -s $FILE ] #文件大小不为0字节 |
5 |
[ -r $FILE ] #文件对当前用户可读。相应的,还有 -w 可写、-x 可执行。如果$FILE是目录,-x表示是否可进入。 |
6 |
[ $FILE1 -nt $FILE2 ] # $FILE1比$FILE2更新。-ot 表示更老。 |
整数判断:
1 |
[ $M - eq $N ] # M == N |
2 |
[ $M - ne $N ] # M != N |
3 |
[ $M -gt $N ] # M > N |
4 |
[ $M -lt $N ] # M < N |
5 |
[ $M - ge $N ] # M >= N |
6 |
[ $M - le $N ] # M <= N |
所有的判断,都可以在[ 后加 ! 表示非操作。
另外,中括号[ ] 可以换成 [[ ]](实际上推荐使用[[ ]],不过它并不是被所有shell所支持),两者的区别如下:
[ ]是“old test”,而 [[ ]] 是“new test”,可以看做 [ ] 的升级版。
[[ ]] 整数判断中同时支持 == 和 =,而[ ]只支持 =;
[[ ]] 可以防止含有空格的变量被分割,因此变量不用双引号包裹起来也是安全的。而[ ]中,变量不用双引号包裹时,如果它的值含有空格,就会引起语法错误(或意想不到的情况);
[[ ]] 文件判断时,通配符将不被bash展开,而是以字面符形式传递给 [[ ;//关于wildcard的详情,请参看下一段落。
[[ ]] 支持在一个[[ ]] 中直接使用 && || 来连接多个test,使用( )括号来指定与或的优先级。而使用 [ ] 的话,通常的做法都是用 && || 来连接多个 [] 块;
[[ ]] 支持使用 =~ 进行正则表达式测试。
更多详情,可以参考此链接:http://mywiki.wooledge.org/BashFAQ/031
先讲最常用的for循环。
range based for loop:
1 |
for i in 1 2 3 4 5 #直接使用数组也是ok的,关于数组,请继续看下文。 |
2 |
do |
3 |
echo "Welcome $i times" |
4 |
done |
1 |
for i in {1..5} #指定 [start, end]区间。由于bash只支持整数,所以当然步进值为1咯 |
2 |
do |
3 |
echo "Welcome $i times" |
4 |
done |
1 |
for i in {0..10..2} #bash 4.0+ 可以自己指定步进值 |
2 |
do |
3 |
echo "Welcome $i times" |
4 |
done |
C语言风格的for loop:
1 |
MAX=5 |
2 |
for (( c=1; c<=MAX; c++ )) ##变量名前可以不加$ |
3 |
do |
4 |
echo "Welcome $c times" |
5 |
done |
for循环中支持使用break和continue,跟C语言中一样。
接着是while循环:
1 |
while condition |
2 |
do |
3 |
: #blah blah |
4 |
done |
可以看到,就首行跟for有点区别,主体部分都是do … done,同样,它也支持break和continue。
星号 asterisk *,表示0个或多个字符
问号 question mark ?,表示1个字符
通配符在判断语句中,行为比较复杂,我们没必要吹毛求疵弄清楚每一种情况,上文也提到用[[ ]]代替[ ],所以这里只讨论[[ ]]中通配符的规则。
作为字符串判断时:
wildcard在引号中,那它就是字面符,不作为通配符使用;
wildcard不在引号中,那它作为通配符使用。
作为文件判断时:
wildcard不管在不在引号中,都视作字面符,不会被bash展开。
所以:试图用*或?进行文件判断的做法,是不可行的。具体可以参考这个链接:
http://stackoverflow.com/questions/24615535/bash-check-if-file-exists-with-double-bracket-test-and-wildcards
注:什么叫做“被bash展开”?看一个例子就知道了:
假设写了如下的判断语句:
1 |
[ -f /*.txt ] |
本意是想匹配是否有txt文件存在,如果/目录正好有且只有一个非隐藏的txt文件(或指向一个文件的symlink)存在,假设这个文件叫file.txt,则该语句被展开为 [ -f /file.txt ],同时它return true;如果/目录有多个txt存在,假设是file1.txt、file2.txt,那该语句被展开为 [ -f /file1.txt /file2.txt ],[ 命令将会报错说传递了太多参数过来。
没错,bash支持一维数组。
1 |
ARRAY=(1 2 3 4) #使用圆括号方式赋初值 |
2 |
echo $ARRAY # 输出第0个元素,即1。$ARRAY等价于${ARRAY},等价于${ARRAY[0]} |
3 |
echo ${ARRAY[1]} #输出第一个元素,即2。这里花括号不能省略 |
4 |
echo ${ARRAY[@]} #输出所有元素,即1 2 3 4。类似的,@可以换成星号 * |
5 |
echo ${ #ARRAY[@]} #输出元素个数,即4。同样,@可以换成星号 * |
6 |
unset ARRAY[1] #删除第1个元素 |
7 |
ARRAY[1]=2 #再把2插入到索引1处,注意是插入,不是替换。 |
8 |
echo ${ARRAY[@]} #元素依旧是 1 2 3 4 |
有两种书写形式:
1 |
function foo { |
2 |
echo "hehe" |
3 |
} |
1 |
foo() { |
2 |
echo "hehe" |
3 |
} |
函数必须在调用之前定义。
函数可以用return语句返回整数(只能是整数)。如果没有return语句,或只写一个return,则返回的数值为最后一个语句的返回值(默认为0,表示成功)。为了防止不可预料的情况发生,建议return x语句不要省略。
调用处使用 $? 来获得返回值。
如何获得函数在stdout中输出的字符串——使用 $(…)形式调用函数。例:
1 |
test () { |
2 |
echo "hehe" |
3 |
return 123 |
4 |
} |
5 |
output=$( test ) #或 `test` |
6 |
result=$? |
7 |
echo $output |
8 |
echo $result |
1 |
echo PLEASE ENTER SOMETHING |
2 |
read NAME #用户输入的内容赋值给NAME变量 |
3 |
echo $NAME |
直接使用read会使得用户输入的内容同时被打印到屏幕,如果想输入的时候不显示内容(例如输入密码),使用:
1 |
read -s NAME |
将某个变量导出(导出的只是一份拷贝,不是引用,因此子shell中对从父shell中继承的变量进行的修改,是无法反映到父shell中的。),使得可以在子shell中使用。前文在讲到readonly时提到,readonly类型的变量导出到子shell后,readonly属性将丧失,很明显,同时也会丧失“可导出”属性。
我们来理解一下/etc/rc.local 中经常写的 export PATH=$PATH:/home/someone/
以上命令的作用是,给PATH增加一个搜索路径后,重新导出,export是必须要加的。
开头提过,可以用declare -x 直接声明为导出变量;也可以随时用export将某个变量导出。
1 |
export NUM=1 #变量赋值后导出 |
2 |
export COUNT |
3 |
COUNT=2 #先导出(不管有没有定义),再赋值 |
关于export,更多详情请参考:http://roclinux.cn/?p=1277
set命令参数很多,不一一列举,这里只讲最常用的。
不带任何参数时,set 会输出当前shell所有的变量,包括所有函数以及从父shell导出的变量。
set -e,表示 exit if any sub command fails。
env不带任何参数时,会输出当前shell的环境变量,不包括函数。
env -i command,-i参数表示 Start with an empty environment, ignoring the inherited environment.
测试代码如下:
test.sh:
1 |
echo "FUCK=" $FUCK |
调用:
1 |
export FUCK=fuck #导出FUCK变量 |
2 |
bash test .sh #输出FUCK=fuck |
3 |
env -i bash test .sh #输出FUCK= |
exit命令的作用是退出当前shell,并返回错误码(不写exit时,返回最后一行代码的错误码。默认为0)。
其返回值也是使用 $? 来获取。
跟return类似,省略exit,或者只写exit而不带数值时,返回的是前一个命令的返回值。
所以为了避免一些不可预见的错误发生,exit语句最好不要省略。