bash快速入门手册

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

本文目的:bash速查,快速上手。
最后更新:2016年09月03日

目录

  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
  20. 常用技巧
  • 执行bash脚本
bash xxx.sh  #执行xxx.sh文件
bash -c command #以字符串方式直接执行。-c 的c代表command。如果command有参数(即有空格),需要用引号包裹。

可以在执行脚本前预先设置变量:

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脚本,首行都是:

#!/bin/bash

如果你的默认shell就是bash,或者你使用 bash xxx.sh方式打开的话,这行可以省略。
但如果别人来执行你的脚本,或者默认shell不是bash呢?
为了确保你的代码始终是用bash执行(通常你仅会为一种shell进行代码测试),加上这行很有必要

额外的,注意一下 /etc/rc.local 文件的首行,你会发现长这样:

#!/bin/sh -e

多了个 -e 参数,表示一旦某条命令执行出错(返回值不为0),立即退出该脚本。
也可以使用 set -e 来达到同样的目的。下文会有关于 set 的更多详情介绍。

  • 分号

通常一行只写一个语句,此时分号可以省略。但如果需要在一行中写多个语句,分号必须写
例:

if [ 2 -eq 2 ]; then echo =; else echo !=; fi
  • 注释行

使用井号 #

  • 空行,no-op line

使用冒号 :

  • 变量声明

使用declare(或typeset,两者完全等价。同时typeset也被zsh所支持)进行变量声明:

declare STRING_VAL  #声明一个字符串变量
declare -r READONLY_VAL  #声明只读变量,也可以写作 readonly READONLY_VAL
declare -i INTEGER1  #声明整型变量
declare -a ARRAY1 #声明数组变量
declare -f  #打印出所有function
declare -f FUNC_NAME  #打印出指定名称的function
declare -x VAR_TO_EXPORT  #声明导出变量。跟使用export命令效果一样。
declare -i -r ABC=2  #在声明变量类型的时候可以直接赋值

readonly的变量如果被导出到子shell,子shell拿到的拷贝将会失去readonly属性

  • 变量赋值

例:FILE_NAME=’abc.txt’
变量名不能使用保留字符,等号两侧不能有空格(否则会被认为是一个shell命令,而非赋值)。
被赋值的变量可以从未被声明。从未被声明过的变量默认是字符串类型。

declare A=123 # 声明一个字符串变量A,并赋值为"123"

用字符串对一个整型变量进行赋值时,不会报错,但结果却是不可预料的。也就是说赋值操作不会改变变量被声明时指定的类型。例:

declare -i NUMBER
NUMBER=abc
echo $NUMBER  #输出 0

 

  • 变量类型

没有用declare -i 声明为整形的变量,统统是字符串变量。
当然,即便是字符串变量,在整数运算时还是会被解释成整数。

  • 变量作用域

当省略declare时,不管位于函数外还是函数内,变量的作用域为程序结束;
当在函数内部使用declare时,作用域为函数结束
当在函数外部使用declare时,作用域为程序结束。

另外,在函数内,declare还有个别名,叫local,用这货能减少歧义。
所以,local也能使用declare的参数。

例:

function func {
local -i HELLO=123
HELLO+=1
echo $HELLO  #输出124
}
  • 引用变量

使用$符号加变量名进行变量引用。
例:FILE_NAME1=$FILE_NAME
标准的写法是${FILE_NAME},简写时可以省略花括号,但某些会引起歧义的地方,还是得用标准写法。比如引用数组变量(下文对数组有详述)。

  • 常用的保留变量

$HOME:当前用户的根目录路径
$PATH:PATH环境变量
$PWD:当前工作路径
$0,$1,$2,…:第0个参数(shell脚本自身),第1个参数……
$#:参数的个数(不包括$0)
$?:获取最近一次执行的脚本、程序或函数的返回值。正常为0,非0为错误。比如command not found时,返回值就是127
$$:该脚本程序的进程号
$RANDOM:1-65536之间的整数
$@:获取本脚本程序/函数所有传入参数。
$:同$@。但用双引号包裹后意义不同,$@、”$@”、$ 展开为原始参数,”$*”展开为一个字符串,内容为原始参数。
以下为示例代码:

function showArgsCount {

echo "args count:$#"

}

function passArgs {

showArgsCount "$*
"

}

function passArgs2 {

showArgsCount "$@
"

}


passArgs a b c
passArgs2 a b c

 

输出结果为:

args count:1
args count:3

 

  • 字符串操作

详情请点击:http://tldp.org/LDP/abs/html/string-manipulation.html
这里只记录几个常用的用法。

STR=abcdefg
POS=2
LEN=3
echo ${#STR} # 输出7,$STR变量的strlen();
echo ${STR:2} # 输出cdefg,从position 2处开始取substring
echo ${STR:2:3} # 输出cde,从position 2处开始取长度为3的substring
echo ${STR:$POS:$LEN} #输出cde,position和length都可以是变量
echo ${@:2} #从此shell脚本传入args中取substring,@可以换成*

${string#substring}

从string中删除一个最短匹配,从开头往后匹配。#换成%,表示从结尾往前匹配。

${string##substring}

从string中删除一个最长匹配,从开头往后匹配。##换成%%,表示从结尾往前匹配。
例:

 

stringZ=abcABC123ABCabc
#       |----|          shortest
#       |----------|    longest

echo ${stringZ#a*C}      # 123ABCabc
# Strip out shortest match between 'a' and 'C'.

echo ${stringZ##a*C}     # abc
# Strip out longest match between 'a' and 'C'.



# You can parameterize the substrings.

X='a*C'

echo ${stringZ#$X}      # 123ABCabc
echo ${stringZ##$X}     # abc
                        # 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进行整数运算,运算符支持 + – * / %
例:

NUM=12
let NUM=$NUM+1
echo $NUM

输出13。或者用expr来做:

NUM=12
NUM=`expr $NUM + 1`
echo $NUM

两者等价。需要注意的是,let方式+两侧不能留空格,要留空格的话,需要把整段用引号包裹;而expr方式中,+号左右必须要留空格。所以结论是:let和expr都挺难用的。不过别急,还有另一种方式:

(( … )) 方式。例:

a=1
b=2
(( c=a+b ))
echo $c

结果会输出3。
(( ))中的操作都会被处理成整数运算,由于不会被解释成字符串,因此引号和变量前的$符号都是不强制的
此外,还支持 +=、-=、*=、/=、%=、>、< 等操作符,操作符左右的空格也是想加就加,怎么样,跟高级语言一样爽歪歪吧。

另外值得一提的是,$(( … ))才是这货的标准形式(POSIX标准)。

bash不支持浮点运算,如果上面的let例子中,NUM=12.1,那+1运算就会报错。实在想处理浮点数的话,需要借助外部命令:dc、awk或者bc。

猛击此链接查看更多关于整数运算的资料:
http://mywiki.wooledge.org/ArithmeticExpression

  • 条件控制

先讲最基本的if – fi 语句。基本语法:

if condition; then
    echo hehe
    echo haha
else
    echo haha
    echo hehe
fi
if condition; then
   :
elif condition; then
   :
else
   :
fi

如果then/else分支不需要写代码,则用冒号 : 来占位

condition表示一个判断语句。

然后讲讲case – esac(很明显,case倒着写就是esac)语句。
这货类似于switch case。
基本语法:

case $val in
  val1) command ;; #分支1
  val2 | val3) command ;;  #分支2或3
  *) command ;;  #default
esac

 

  • 判断语句

判断语句的返回值是true or false。

字符串判断:

[ $VAR1 = $VAR2 ]  #两个字符串是否相等。另外,!= 表示不相等,> < 分别表示 大于小于。
[ -z $VAR ]  #是否为空串(未定义的变量也属于空串)。
[[ -n $VAR ]]  #跟-z相反,表示not zero length,用来判断某个变量是否被定义(不为空串)。需要注意的是,-n 必须使用[[ ]],具体原因未知,这是本人亲测的结果。
[ $VAR ]  #是否不为空串

最常用的字符串beginwith和endwith可以这样用:

if [[ $VAR == abc* ]]; then
  echo "$VAR begin with abc"
fi

if [[ $VAR == *abc ]]; then
  echo "$VAR end with abc"
fi

文件判断:

[ -e $FILE ] #文件是否存在
[ -f $PATH ] #是否是普通文件(非dir或symbol link之类)
[ -d $PATH ] #是否是目录
[ -s $FILE ] #文件大小不为0字节
[ -r $FILE ] #文件对当前用户可读。相应的,还有 -w 可写、-x 可执行。如果$FILE是目录,-x表示是否可进入。
[ $FILE1 -nt $FILE2 ] # $FILE1比$FILE2更新。-ot 表示更老。

整数判断:

[ $M -eq $N ] # M == N
[ $M -ne $N ] # M != N
[ $M -gt $N ] # M > N
[ $M -lt $N ] # M < N
[ $M -ge $N ] # M >= N
[ $M -le $N ] # M <= N

所有的判断,都可以在[ 后加 ! 表示非操作

另外,中括号[ ] 可以换成 [[ ]](推荐使用[[ ]],虽然它并不是被所有shell所支持),两者的区别如下:

[ ]是“old test”,而 [[ ]] 是“new test”,可以看做 [ ] 的升级版。
[[ ]] 字符串判断中同时支持 == 和 =,而[ ]只支持 =;
[[ ]] 可以防止含有空格的变量被分割,因此变量不用双引号包裹起来也是安全的。而[ ]中,变量不用双引号包裹时,如果它的值含有空格,就会引起语法错误(或意想不到的情况);
[[ ]] 文件判断时,通配符将不被bash展开,而是以字面符形式传递给 [[ ;//关于wildcard的详情,请参看下一段落。
[[ ]] 支持在一个[[ ]] 中直接使用 && || 来连接多个test,使用( )括号来指定与或的优先级。而使用 [ ] 的话,通常的做法都是用 && || 来连接多个 [] 块;
[[ ]] 支持使用 =~ 进行正则表达式测试。

更多详情,可以参考此链接:http://mywiki.wooledge.org/BashFAQ/031

  • 循环控制

先讲最常用的for循环。

range based for loop:

array1=(a b c d e) #定义一个数组,关于数组,请继续看下文。
for i in ${array1[@]} #遍历数组中的每一个元素
do
   echo "Welcome $i"
done
for i in 1 2 3 4 5  #直接使用字面数组也是ok的,关于数组,请继续看下文。
do
   echo "Welcome $i times"
done
for i in {1..5}  #指定 [start, end]区间。由于bash只支持整数,所以当然步进值为1咯 
do
   echo "Welcome $i times"
done
for i in {0..10..2}  #bash 4.0+ 可以自己指定步进值
do
     echo "Welcome $i times"
done

C语言风格的for loop:

MAX=5
for (( c=1; c<=MAX; c++ ))  ##变量名前可以不加$
do
   echo "Welcome $c times"
done

for循环中支持使用break和continue,跟C语言中一样。

接着是while循环:

while condition
do
  : #blah blah
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展开”?看一个例子就知道了:
假设写了如下的判断语句:

[ -f /*.txt ]

本意是想匹配是否有txt文件存在,如果/目录正好有且只有一个非隐藏的txt文件(或指向一个文件的symlink)存在,假设这个文件叫file.txt,则该语句被展开为 [ -f /file.txt ],同时它return true;如果/目录有多个txt存在,假设是file1.txt、file2.txt,那该语句被展开为 [ -f /file1.txt /file2.txt ],[ 命令将会报错说传递了太多参数过来。

  • 数组

没错,bash支持一维数组。

ARRAY=(1 2 3 4)  #使用圆括号方式赋初值,每个元素使用空格分开
echo $ARRAY  # 输出第0个元素,即1。$ARRAY等价于${ARRAY},等价于${ARRAY[0]}
echo ${ARRAY[1]} #输出第一个元素,即2。这里花括号不能省略
echo ${ARRAY[@]} #输出所有元素,即1 2 3 4。类似的,@可以换成星号 *
echo ${#ARRAY[@]} #输出元素个数,即4。同样,@可以换成星号 *
unset ARRAY[1] #删除第1个元素
ARRAY[1]=2 #再把2插入到索引1处,注意是插入,不是替换。
echo ${ARRAY[@]}  #元素依旧是 1 2 3 4
ARRAY+=(5)  #push back一个元素到数组尾部,注意,元素必须用()包裹
  • bash函数

【函数定义】
有多种书写形式:

function foo {
  echo "hehe"
}
function foo () {
  echo "hehe"
}
foo() {
  echo "hehe"
}

函数必须在调用之前定义
函数可以用return语句返回整数(只能是整数)。如果没有return语句,或只写一个return,则返回的数值为最后一个语句的返回值(默认为0,表示成功)。为了防止不可预料的情况发生,建议return x语句不要省略

【函数调用】
例:

test() {
  echo "hehe"
  return 123
}
output=$(test)  #或 `test`
result=$?
echo $output
echo $result

output=$(func “args”),或者output=func "args",通过这种形式来调用函数。
$output的内容为func函数中输出到stdout的内容。
$? 用来获取最近一次调用的函数return的值。

  • 获取用户输入
echo PLEASE ENTER SOMETHING
read NAME  #用户输入的内容赋值给NAME变量
echo $NAME

直接使用read会使得用户输入的内容同时被打印到屏幕,如果想输入的时候不显示内容(例如输入密码),使用:

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将某个变量导出。

export NUM=1  #变量赋值后导出
export COUNT
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:

echo "FUCK="$FUCK

调用:

export FUCK=fuck  #导出FUCK变量
bash test.sh  #输出FUCK=fuck
env -i bash test.sh  #输出FUCK=

 

  • exit

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

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

  • 常用技巧

获取当前sh文件的绝对路径
通常执行sh时,首先cd到所在目录,但要是使用者不按常理出牌,如何保证从任意路径执行该sh时,都能正常工作呢?
那就只能在sh内部自己cd了。

SCRIPTPATH=$( cd $(dirname $0) ; pwd -P )
cd $SCRIPTPATH

bash快速入门手册》有2个想法

    1. 哈,很高兴能帮到你。本文目的就是为了速查和快速上手。我自己也经常回来翻了看。
      有需要纠正和补充的(特别是常用技巧)可以留言,我会更新正文,会备注投稿者名称的 🙂

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据