BASH快速入门手册

转载地址:https://laoyur.com/?p=638


精心整理之作,转载请注明出处 

本文目的:bash速查,快速上手。

目录

  1. 执行bash脚本
  2. 单双引号
  3. 首行
  4. 分号
  5. 注释行
  6. 空行
  7. 变量
    1. 变量声明
    2. 变量赋值
    3. 变量类型
    4. 变量作用域
    5. 引用变量
    6. 常用保留变量
  8. 字符串操作
  9. 整数运算
  10. 条件控制
  11. 判断语句
  12. 循环控制
  13. 通配符
  14. 数组
  15. 函数
  16. 获取用户输入
  17. export
  18. set & env
  19. exit
  • 执行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,原因是啥?请继续看下文。

  • 单引号 VS 双引号

单引号中的字符串不进行转义,原样输出;双引号中的内容会被转义。
例: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
  • 注释行

使用井号 #

  • 空行,no-op line

使用冒号 :

  • 变量声明

使用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 in 1 2 3 4 5  #直接使用数组也是ok的,关于数组,请继续看下文。
2 do
3    echo "Welcome $i times"
4 done
1 for in {1..5}  #指定 [start, end]区间。由于bash只支持整数,所以当然步进值为1咯
2 do
3    echo "Welcome $i times"
4 done
1 for 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。

 

  • 通配符 wildcard

星号 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
  • bash函数

有两种书写形式:

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
  • export

将某个变量导出(导出的只是一份拷贝,不是引用,因此子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 & env

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

exit命令的作用是退出当前shell,并返回错误码(不写exit时,返回最后一行代码的错误码。默认为0)。
其返回值也是使用 $? 来获取。

跟return类似,省略exit,或者只写exit而不带数值时,返回的是前一个命令的返回值。
所以为了避免一些不可预见的错误发生,exit语句最好不要省略


你可能感兴趣的:(BASH快速入门手册)