Bash入门

什么是bash

Bash 是 Unix 系统和 Linux 系统的一种 Shell(命令行环境),是目前绝大多数 Linux 发行版的默认 Shell

什么是shell

学习 Bash,首先需要理解 Shell 是什么。Shell 这个单词的原意是“外壳”,跟 kernel(内核)相对应,比喻内核外面的一层,即用户跟内核交互的对话界面。

首先,Shell 是一个程序,提供一个与用户对话的环境。这个环境只有一个命令提示符,让用户从键盘输入命令,所以又称为命令行环境(command line interface,简写为 CLI)。Shell 接收到用户输入的命令,将命令送入操作系统执行,并将结果返回给用户。

查看当前使用是shell是什么

echo $SHELL
/bin/bash

当前正在使用的 Shell 不一定是默认 Shell,一般来说,ps命令结果的倒数第二行是当前 Shell。

$ ps
PID TTY TIME CMD
4467 pts/0 00:00:00 bash
5379 pts/0 00:00:00 ps

查看当前所有安装的Shell

cat /etc/shells

退出和进入bash

# 进入bash
bash
# 退出bash 或者 Ctrl+d
exit

bash基础

echo命令

echo命令可以在输入一行文本

echo hello world
hello world

-n参数

默认情况下,echo输出的文本末尾会有一个回车符。-n参数可以取消末尾的回车符,使得下一个提示符紧跟在输出内容的后面

-e参数

-e参数会解释引号(双引号和单引号)里面的特殊字符(比如换行符\n)。如果不使用-e参数,即默认情况下,引号会让特殊字符变成普通字符,echo不解释它们,原样输出

$ echo "Hello\nWorld"
Hello\nWorld

# 双引号的情况
$ echo -e "Hello\nWorld"
Hello
World

# 单引号的情况
$ echo -e 'Hello\nWorld'
Hello
World

bash命令格式

$ command [ arg1 ... [ argN ]]

# 短形式
$ ls -r

# 长形式
$ ls --reverse

$ echo foo bar

# 等同于
$ echo foo \
bar

分号

分号(;)是命令的结束符,使得一行可以放置多个命令,上一个命令执行结束后,再执行第二个命令

命令的组合符&&||

Command1 && Command2

上面命令的意思是,如果Command1命令运行成功,则继续运行Command2命令。

Command1 || Command2

上面命令的意思是,如果Command1命令运行失败,则继续运行Command2命令。

快捷键

  • Ctrl + L:清除屏幕并将当前行移到页面顶部。
  • Ctrl + C:中止当前正在执行的命令。
  • Shift + PageUp:向上滚动。
  • Shift + PageDown:向下滚动。
  • Ctrl + U:从光标位置删除到行首。
  • Ctrl + K:从光标位置删除到行尾。
  • Ctrl + W:删除光标位置前一个单词。
  • Ctrl + D:关闭 Shell 会话。
  • :浏览已执行命令的历史记录。

模式拓展

关闭拓展

$ set -o noglob
# 或者
$ set -f

重新打开拓展

$ set +o noglob
# 或者
$ set +f

?字符拓展

?字符代表文件路径里面的任意单个字符,不包括空字符。比如,Data???匹配所有Data后面跟着三个字符的文件名。

# 存在文件 a.txt 和 b.txt
$ ls ?.txt
a.txt b.txt

上面命令中,?表示单个字符,所以会同时匹配a.txtb.txt

如果匹配多个字符,就需要多个?连用。

# 存在文件 a.txt、b.txt 和 ab.txt
$ ls ??.txt
ab.txt

上面命令中,??匹配了两个字符。

? 字符扩展属于文件名扩展,只有文件确实存在的前提下,才会发生扩展。如果文件不存在,扩展就不会发生。

* 字符扩展

*字符代表文件路径里面的任意数量的任意字符,包括零个字符。

# 存在文件 a.txt、b.txt 和 ab.txt
$ ls *.txt
a.txt b.txt ab.txt

上面例子中,*.txt代表后缀名为.txt的所有文件。

如果想输出当前目录的所有文件,直接用*即可。

$ ls *

*可以匹配空字符,下面是一个例子。

# 存在文件 a.txt、b.txt 和 ab.txt
$ ls a*.txt
a.txt ab.txt

$ ls *b*
b.txt ab.txt

注意,*不会匹配隐藏文件(以.开头的文件),即ls *不会输出隐藏文件。

如果要匹配隐藏文件,需要写成.*

如果要匹配隐藏文件,同时要排除...这两个特殊的隐藏文件,可以与方括号扩展结合使用,写成.[!.]*

$ echo .[!.]*

[…]拓展

# 存在文件 a.txt 和 b.txt
$ ls [ab].txt
a.txt b.txt

# 只存在文件 a.txt
$ ls [ab].txt
a.txt

[start-end] 扩展

# 存在文件 a.txt、b.txt 和 c.txt
$ ls [a-c].txt
a.txt
b.txt
c.txt

# 存在文件 report1.txt、report2.txt 和 report3.txt
$ ls report[0-9].txt
report1.txt
report2.txt
report3.txt
...

长用例子:

  • [a-z]:所有小写字母。
  • [a-zA-Z]:所有小写字母与大写字母。
  • [a-zA-Z0-9]:所有小写字母、大写字母与数字。
  • [abc]*:所有以abc字符之一开头的文件名。
  • program.[co]:文件program.c与文件program.o
  • BACKUP.[0-9][0-9][0-9]:所有以BACKUP.开头,后面是三个数字的文件名。

{start..end} 扩展

# 允许逆序
$ echo {a..c}
a b c

$ echo d{a..d}g
dag dbg dcg ddg

$ echo {1..4}
1 2 3 4

$ echo Number_{1..5}
Number_1 Number_2 Number_3 Number_4 Number_5

#这种简写形式还可以使用第二个双点号(start..end..step),用来指定扩展的步长。

$ echo {0..8..2}
0 2 4 6 8

变量扩展

Bash 将美元符号$开头的词元视为变量

字符类

[[:class:]]表示一个字符类,扩展成某一类特定字符之中的一个。常用的字符类如下。

  • [[:alnum:]]:匹配任意英文字母与数字
  • [[:alpha:]]:匹配任意英文字母
  • [[:blank:]]:空格和 Tab 键。
  • [[:cntrl:]]:ASCII 码 0-31 的不可打印字符。
  • [[:digit:]]:匹配任意数字 0-9。
  • [[:graph:]]:A-Z、a-z、0-9 和标点符号。
  • [[:lower:]]:匹配任意小写字母 a-z。
  • [[:print:]]:ASCII 码 32-127 的可打印字符。
  • [[:punct:]]:标点符号(除了 A-Z、a-z、0-9 的可打印字符)。
  • [[:space:]]:空格、Tab、LF(10)、VT(11)、FF(12)、CR(13)。
  • [[:upper:]]:匹配任意大写字母 A-Z。
  • [[:xdigit:]]:16进制字符(A-F、a-f、0-9)

变量

显示所有变量

通过set可以显示所有变量

创建变量

a=z                     # 变量 a 赋值为字符串 z
b="a string" # 变量值包含空格,就必须放在引号里面
c="a string and $b" # 变量值可以引用其他变量的值
d="\t\ta string\n" # 变量值可以使用转义字符
e=$(ls -l foo.txt) # 变量值可以是命令的执行结果
f=$((5 * 7)) # 变量值可以是数学运算的结果

读取变量

$ foo=bar
$ echo $foo
bar

删除变量

unset NAME

输出变量,export 命令

用户创建的变量仅可用于当前 Shell,子 Shell 默认读取不到父 Shell 定义的变量。为了把变量传递给子 Shell,需要使用export命令。这样输出的变量,对于子 Shell 来说就是环境变量。

declare 命令

declare命令可以声明一些特殊类型的变量,为变量设置一些限制,比如声明只读类型的变量和整数类型的变量。

它的语法形式如下。

declare OPTION VARIABLE=value

declare命令的主要参数(OPTION)如下。

  • -a:声明数组变量。
  • -f:输出所有函数定义。
  • -F:输出所有函数名。
  • -i:声明整数变量。
  • -l:声明变量为小写字母。
  • -p:查看变量信息。
  • -r:声明只读变量。
  • -u:声明变量为大写字母。
  • -x:该变量输出为环境变量。

declare命令如果用在函数中,声明的变量只在函数内部有效,等同于local命令。

不带任何参数时,declare命令输出当前环境的所有变量,包括函数在内,等同于不带有任何参数的set命令。

let 命令

let命令声明变量时,可以直接执行算术表达式。

$ let foo=1+2
$ echo $foo
3

上面例子中,let命令可以直接计算1 + 2

let命令的参数表达式如果包含空格,就需要使用引号。

$ let "foo = 1 + 2"

字符串操作

获取字符串长度

${#varname}

提取子字符串

${varname:offset:length}

搜索和替换

# 如果 pattern 匹配变量 variable 的开头,
# 删除最短匹配(非贪婪匹配)的部分,返回剩余部分
${variable#pattern}

# 如果 pattern 匹配变量 variable 的开头,
# 删除最长匹配(贪婪匹配)的部分,返回剩余部分
${variable##pattern}

改变大小写

# 转为大写
${varname^^}

# 转为小写
${varname,,}

算术运算

((...))会自动忽略内部的空格,所以下面的写法都正确,得到同样的结果。

$ ((foo = 5 + 5))
$ echo $foo
10

进制数

Bash 的数值默认都是十进制,但是在算术表达式中,也可以使用其他进制。

  • number:没有任何特殊表示法的数字是十进制数(以10为底)。
  • 0number:八进制数。
  • 0xnumber:十六进制数。
  • base#numberbase进制的数。

位运算

$((...))支持以下的二进制位运算符。

  • <<:位左移运算,把一个数字的所有位向左移动指定的位。
  • >>:位右移运算,把一个数字的所有位向右移动指定的位。
  • &:位的“与”运算,对两个数字的所有位执行一个AND操作。
  • |:位的“或”运算,对两个数字的所有位执行一个OR操作。
  • ~:位的“否”运算,对一个数字的所有位取反。
  • ^:位的异或运算(exclusive or),对两个数字的所有位执行一个异或操作。

逻辑运算

$((...))支持以下的逻辑运算符。

  • <:小于
  • >:大于
  • <=:小于或相等
  • >=:大于或相等
  • ==:相等
  • !=:不相等
  • &&:逻辑与
  • ||:逻辑或
  • !:逻辑否
  • expr1?expr2:expr3:三元条件运算符。若表达式expr1的计算结果为非零值(算术真),则执行表达式expr2,否则执行表达式expr3

如果逻辑表达式为真,返回1,否则返回0

expr 命令

expr命令支持算术运算,可以不使用((...))语法。

expr命令也不支持非整数参数。

let 命令

let命令用于将算术运算的结果,赋予一个变量。

let x=2+3
$ echo $x
5

脚本入门

Shebang行

脚本的第一行通常是指定解释器,即这个脚本必须通过什么解释器执行。这一行以#!字符开头,这个字符称为 Shebang,所以这一行就叫做 Shebang 行。

#!后面就是脚本解释器的位置,Bash 脚本的解释器一般是/bin/sh/bin/bash

#!/bin/sh
# 或者
#!/bin/bash

#!与脚本解释器之间有没有空格,都是可以的。

如果 Bash 解释器不放在目录/bin,脚本就无法执行了。为了保险,可以写成下面这样。

#!/usr/bin/env bash

执行权限和路径

# 给所有用户执行权限
$ chmod +x script.sh

# 给所有用户读权限和执行权限
$ chmod +rx script.sh
# 或者
$ chmod 755 script.sh

# 只给脚本拥有者读权限和执行权限
$ chmod u+rx script.sh

脚本参数

调用脚本的时候,脚本文件名后面可以带有参数。

$ script.sh word1 word2 word3

上面例子中,script.sh是一个脚本文件,word1word2word3是三个参数。

脚本文件内部,可以使用特殊变量,引用这些参数。

  • $0:脚本文件名,即script.sh
  • $1~`$9`:对应脚本的第一个参数到第九个参数。
  • $#:参数的总数。
  • $@:全部的参数,参数之间使用空格分隔。
  • $*:全部的参数,参数之间使用变量$IFS值的第一个字符分隔,默认为空格,但是可以自定义
#!/bin/bash

for i in "$@"; do
echo $i
done

shift 命令

shift命令可以改变脚本参数,每次执行都会移除脚本当前的第一个参数($1),使得后面的参数向前一位,即$2变成$1$3变成$2$4变成$3,以此类推。

while循环结合shift命令,也可以读取每一个参数。

#!/bin/bash

echo "一共输入了 $# 个参数"

while [ "$1" != "" ]; do
echo "剩下 $# 个参数"
echo "参数:$1"
shift
done

exit命令

exit命令用于终止当前脚本的执行,并向 Shell 返回一个退出值。

# 退出值为0(成功)
$ exit 0

# 退出值为1(失败)
$ exit 1

命令执行结果

命令执行结束后,会有一个返回值。0表示执行成功,非0(通常是1)表示执行失败。环境变量$?可以读取前一个命令的返回

cd /path/to/somewhere
if [ "$?" = "0" ]; then
rm *
else
echo "无法切换目录!" 1>&2
exit 1
fi

source 命令

source命令用于执行一个脚本,通常用于重新加载一个配置文件。

别名alias命令

unalias解除别名

alias命令用来为一个命令指定别名,这样更便于记忆。下面是alias的格式。

alias NAME=DEFINITION

read命令

有时,脚本需要在执行过程中,由用户提供一部分数据,这时可以使用read命令。它将用户的输入存入一个变量,方便后面的代码使用。用户按下回车键,就表示输入结束。

(1)-t 参数

read命令的-t参数,设置了超时的秒数。如果超过了指定时间,用户仍然没有输入,脚本将放弃等待,继续向下执行。

#!/bin/bash

echo -n "输入一些文本 > "
if read -t 3 response; then
echo "用户已经输入了"
else
echo "用户没有输入"
fi

环境变量TMOUT也可以起到同样作用,指定read命令等待用户输入的时间(单位为秒)

2)-p 参数

-p参数指定用户输入的提示信息。

read -p "Enter one or more values > "
echo "REPLY = '$REPLY'"

3)-a 参数

-a参数把用户的输入赋值给一个数组,从零号位置开始。

4)-n 参数

-n参数指定只读取若干个字符作为变量值,而不是整行读取。

5)-e 参数

-e参数允许用户输入的时候,使用readline库提供的快捷键,比如自动补全

6)其他参数

  • -d delimiter:定义字符串delimiter的第一个字符作为用户输入的结束,而不是一个换行符。
  • -r:raw 模式,表示不把用户输入的反斜杠字符解释为转义字符。
  • -s:使得用户的输入不显示在屏幕上,这常常用于输入密码或保密信息。
  • -u fd:使用文件描述符fd作为输入

条件判断

if test $USER = "foo"; then
echo "Hello foo."
else
echo "You are not foo."
fi

test命令

if结构的判断条件,一般使用test命令,有三种形式。

# 写法一
test expression

# 写法二
[ expression ]

# 写法三
[[ expression ]]

文件判断

  • [ -a file ]:如果 file 存在,则为true
  • [ -b file ]:如果 file 存在并且是一个块(设备)文件,则为true
  • [ -c file ]:如果 file 存在并且是一个字符(设备)文件,则为true
  • [ -d file ]:如果 file 存在并且是一个目录,则为true
  • [ -e file ]:如果 file 存在,则为true
  • [ -f file ]:如果 file 存在并且是一个普通文件,则为true
  • [ -g file ]:如果 file 存在并且设置了组 ID,则为true
  • [ -G file ]:如果 file 存在并且属于有效的组 ID,则为true
  • [ -h file ]:如果 file 存在并且是符号链接,则为true
  • [ -k file ]:如果 file 存在并且设置了它的“sticky bit”,则为true
  • [ -L file ]:如果 file 存在并且是一个符号链接,则为true
  • [ -N file ]:如果 file 存在并且自上次读取后已被修改,则为true
  • [ -O file ]:如果 file 存在并且属于有效的用户 ID,则为true
  • [ -p file ]:如果 file 存在并且是一个命名管道,则为true
  • [ -r file ]:如果 file 存在并且可读(当前用户有可读权限),则为true
  • [ -s file ]:如果 file 存在且其长度大于零,则为true
  • [ -S file ]:如果 file 存在且是一个网络 socket,则为true
  • [ -t fd ]:如果 fd 是一个文件描述符,并且重定向到终端,则为true。 这可以用来判断是否重定向了标准输入/输出/错误。
  • [ -u file ]:如果 file 存在并且设置了 setuid 位,则为true
  • [ -w file ]:如果 file 存在并且可写(当前用户拥有可写权限),则为true
  • [ -x file ]:如果 file 存在并且可执行(有效用户有执行/搜索权限),则为true
  • [ FILE1 -nt FILE2 ]:如果 FILE1 比 FILE2 的更新时间更近,或者 FILE1 存在而 FILE2 不存在,则为true
  • [ FILE1 -ot FILE2 ]:如果 FILE1 比 FILE2 的更新时间更旧,或者 FILE2 存在而 FILE1 不存在,则为true
  • [ FILE1 -ef FILE2 ]:如果 FILE1 和 FILE2 引用相同的设备和 inode 编号,则为true

字符串判断

  • [ string ]:如果string不为空(长度大于0),则判断为真。
  • [ -n string ]:如果字符串string的长度大于零,则判断为真。
  • [ -z string ]:如果字符串string的长度为零,则判断为真。
  • [ string1 = string2 ]:如果string1string2相同,则判断为真。
  • [ string1 == string2 ] 等同于[ string1 = string2 ]
  • [ string1 != string2 ]:如果string1string2不相同,则判断为真。
  • [ string1 '>' string2 ]:如果按照字典顺序string1排列在string2之后,则判断为真。
  • [ string1 '<' string2 ]:如果按照字典顺序string1排列在string2之前,则判断为真。

注意,test命令内部的><,必须用引号引起来(或者是用反斜杠转义)。否则,它们会被 shell 解释为重定向操作符。

整数判断

  • [ integer1 -eq integer2 ]:如果integer1等于integer2,则为true
  • [ integer1 -ne integer2 ]:如果integer1不等于integer2,则为true
  • [ integer1 -le integer2 ]:如果integer1小于或等于integer2,则为true
  • [ integer1 -lt integer2 ]:如果integer1小于integer2,则为true
  • [ integer1 -ge integer2 ]:如果integer1大于或等于integer2,则为true
  • [ integer1 -gt integer2 ]:如果integer1大于integer2,则为true

正则判断

[[ string1 =~ regex ]]

循环

while循环

while condition; do
commands
done

until

until循环与while循环恰好相反,只要不符合判断条件(判断条件失败),就不断循环执行指定的语句。一旦符合判断条件,就退出循环

until condition; do
commands
done

for…in 循环

for variable in list
do
commands
done

break,continue

Bash 提供了两个内部命令breakcontinue,用来在循环内部跳出循环。

break命令立即终止循环,程序继续执行循环块之后的语句,即不再执行剩下的循环

函数

函数(function)是可以重复使用的代码片段,有利于代码的复用。它与别名(alias)的区别是,别名只适合封装简单的单个命令,函数则可以封装复杂的多行命令

# 第一种
fn() {
# codes
}

# 第二种
function fn() {
# codes
}
#删除一个函数,可以使用unset命令。
unset -f functionName

#查看当前 Shell 已经定义的所有函数,可以使用declare命令。
declare -f

数组

#创建数组
ARRAY=(value1 value2 ... valueN)

# 等同于

ARRAY=(
value1
value2
value3
)

Set命令

# 执行脚本时,如果遇到不存在的变量,Bash 默认忽略它。
set -u

# 默认情况下,脚本执行后,只输出运行结果,没有其他内容。如果多个命令连续执行,它们的运行结果就会连续输出
set -x

#上面这些写法多少有些麻烦,容易疏忽。set -e从根本上解决了这个问题,它使得脚本只要发生错误,就终止执行
set -e

# -e 在有|情况下不生效 用来解决这种情况,只要一个子命令失败,整个管道命令就失败,脚本就会终止执行
set -o pipefail

set -n:等同于set -o noexec,不运行命令,只检查语法是否正确。
set -f:等同于set -o noglob,表示不对通配符进行文件名扩展。
set -v:等同于set -o verbose,表示打印 Shell 接收到的每一行输入。
set -o noclobber:防止使用重定向运算符>覆盖已经存在的文件

Bash 的错误处理

# 写法一
command || { echo "command failed"; exit 1; }

# 写法二
if ! command; then echo "command failed"; exit 1; fi

# 写法三
command
if [ "$?" -ne 0 ]; then echo "command failed"; exit 1; fi