linux编程-bash
1. 基本语法
1.1 注释
注释以"#"开始, "#"后面的内容被忽略.
说明:
- "#" 前可以没有语句, 表示整行被注释.
- "#" 前可以有语句, 只注释"#"后面的内容.
1.2 变量
$ VAR_NAME='value' # 变量赋值, 注意等号两边不能有空格
$ echo "I am $VAR_NAME, ${VAR_NAME}" # 通过$符使用变量
$
$ LIST=$(ls) # 将shell命令结果赋值给变量,
$ SERVER_NAME=$(hastname) # 参考子命令扩展
全局变量:在脚本中任何位置都可以使用该变量,shell变量默认都是全局变量。
本地变量:可以在函数内部声明本地变量,使用local关键字。
1.3 数组
数组直接赋值, 注意等号两边不能有空格.
$ myarr=() # 定义一个空数组
$ myarr=(1 2 3 4 5) # 数组元素都为数字
$ myarr=(one two three four five) # 数组元素都为字符串
$ myarr=(1 two 3 four 5) # 数组元素由数字和字符串组成
$ myarr=(1 two 3 "a line") # 数组元素包含空格
通过数组下标赋值
$ myarr[0]=1 # 通过下标为数组元素赋值
$ myarr[2]=test
$ myarr[9]="a line" # 下标超出原数组长度也可以直接赋值
获取数组信息
$ myarr=(one two three four five) # 数组元素都为字符串
$
$ echo ${myarr[0]} # 通过下标访问元素
one
$ echo ${myarr[@]} # 打印整个数组
one two three four five
$ echo ${#myarr[*]} # 获取数组长度
5
$ echo ${#myarr[@]} # 同上
5
$ echo ${#myarr[9]} # 获取元素的长度
0
$ echo ${myarr[@]:3:2} # 数组切片, 从第3个元素开始, 元素个数为2
four five
$ myarr+=(six seven) # 给数组追加新数组
$ echo ${myarr[@]}
one two three four five six seven
$ unset ${myarr[9]} # 删除数组某个元素
$ unset myarr # 删除整个数组
通过变量做下标:
$ myarr=(one two three four five)
$ i=0
$ echo ${myarr[i]} # 通过变量做下标
one
$ echo ${myarr[i+1]} # 通过变量运算做下标
two
遍历数组: 通过in关键字, ${myarr[@]}加双引号, 元素中有空格不会被拆分. 如果不加双引号, 最后一个元素中有空格, 会被拆分.
$ myarr=(one two three four "a line")
$ for ele in "${myarr[@]}"; do echo $ele ; done
one
two
three
four
a line
$
遍历数组: 通过下标自增
$ myarr=(one two three four "a line")
$ for ((i=0;i<${#myarr[*]};i++)); do echo ${myarr[i]}; done
one
two
three
four
a line
$
遍历数组: 通过下标自增2
$ for ((i=0;i<${#myarr[*]};i+=2)); do echo "${myarr[i]}, ${myarr[i+1]}"; done
one, two
three, four
a line,
$
1.4 命令行参数
$0 # 脚本名称
$1 # param1
$2 # param2
...
$@ # 整个参数数组,$1 $2 ...,不包括$0。
1.5 获取用户输入
read命令接收键盘的输入
$ read -p "Please Enter Your Name: " NAME
Please Enter Your Name: ZhangSan
$ echo "Your Name Is: $NAME"
Your Name Is: ZhangSan
1.6 文件测试
注意,方括号与中间内容必须有空格。
[ -d FILE_NAME ] # FILE_NAME是dir
[ -e FILE_NAME ] # FILE_NAME存在
[ -f FILE_NAME ] # FILE_NAME存在且是regular file
[ -r FILE_NAME ] # FILE_NAME是readable
[ -s FILE_NAME ] # FILE_NAME存在且不为空
[ -w FILE_NAME ] # FILE_NAME有write permission
[ -x FILE_NAME ] # FILE_NAME是excutable
1.7 字符串测试
注意,方括号与中间内容必须有空格。
[ -z STRING ] # STRING是空的
[ -n STRING ] # STRING不是空的
[ STRING1 = STRING2 ] # 字符串相等
[ STRING1 != STRING2 ]# 字符串不相等
1.8 算术测试
注意,方括号与中间内容必须有空格。
[ var1 -eq var2 ] # 相等
[ var1 -ne var2 ] # 不相等
[ var1 -lt var2 ] # var1 less than var2
[ var1 -le var2 ] # var1 less than or equal to var2
[ var1 -gt var2 ] # var1 greater than var2
[ var1 -ge var2 ] # var1 greater than or equal to var2
1.9 逻辑运算 &&, ||
逻辑与: &&
逻辑或: ||
例子:创建目录tempDir成功后,进入该目录并创建子目录subTempDir
mkdir tempDir && cd tempDir && mkdir subTempDir
1.10 if条件判断
语法1: 只有一个if分支
if [ condition-is-true ]
then # 注意,then不能直接写在if语句行, 除非在if语句行尾加分号后再使用then
command 1
…
command N
fi
语法2: if带elif分支
if [ condition-is-true ]; then
command 1
elif [ condition-is-true ]; then
command 2
elif [ condition-is-true ]; then
command 3
else
command 4
fi
1.11 case条件判断
语法:
case "$VAR" in
pattern_1)
# commands when $VAR matches pattern 1
;;
pattern_2)
# commands when $VAR matches pattern 2
;;
*)
# This will run if $VAR doesnt match any of the given patterns
;;
esac
例子:
read -p “Enter the answer in Y/N: ” ANSWER
case “$ANSWER” in
[yY] | [yY][eE][sS])
echo “the answer is Yes.”
;;
[nN] | [nN][oO])
echo “the answer is No.”
;;
*)
echo “invalid answer .”
;;
esac
1.12 for循环
语法1:
注意: 遍历数组时, 不能直接把数组名写在in后面, 要使用${array[@]} 或 ${array[*]}, 参考1.3节对数组遍历的说明.
for VARIABLE_NAME in ITEM_1 ITEM_N
do
command 1
command 2
...
...
command N
done
例:
$ for V in a b c; do echo $V; done # 注意, 这里使用分号, 是因为把do/done等语句写在一行了, 要用分号进行分隔, 如果写在不同行, 就不需要分号
a
b
c
$
$ COLORS="red green blue"
$ for C in $COLORS; do echo $C ; done
red
green
blue
$
FIELS=$(ls *txt)
NEW="new"
for FILE in $FILES
do
echo "Renaming $FILE to $NEW-$FILE"
mv $FILE $NEW-$FILE
done
语法2:
for (( VAR=1;VAR<N;VAR++ )) # 注意, 有两对小括号, 参考算术扩展.
do
command 1
command 2
...
...
command N
done
例
$ for ((I=0;I<5;I++)); do echo $I; done
0
1
2
3
4
1.13 while循环
语法:
while [ condition-is-true ]
do
command 1
command 2
…
command N
done
例: 按行读取文件/ect/passwd的内容
LINE=1
while read CURRENT_LINE; do # CURRENT_LINE是内置变量, 当前行的内容.
echo "${LINE}: $CURRENT_LINE"
((LINE++)) # 变量自增, 见模式扩展
done < /etc/passwd
1.14 循环控制
continue # 结束本次循环节,进入下一个循环节
break # 结束整个循环
1.15 函数
语法:
function func_name() {
command 1
command 2
...
command N
}
例子:声明一个函数并调用
#!/bin/bash
function myFunc() {
echo "Shell Scripting Is Fun!"
}
myFunc # 函数调用
函数参数
$1 第一个参数
$2 第二个参数
$@ 参数数组
参数之间使用空格分隔
例
#!/bin/bash
function add() {
let "SUM=$1+$2"
return $SUM
}
A=$(add 3 4)
echo "3+4=${A}"
2. 通配符 or 模式扩展
共有8种模式扩展,按优先级顺序分为:
序号 | 名称 | 说明 |
---|---|---|
1 | brace expansion | 大括号 {} |
2 | tilde expansion | 波浪号 ~ |
3 | parameter and variable expansion | 参数? 变量? |
4 | arithmetic expansion | 算术扩展 |
5 | command substitution | 子命令扩展 |
6 | word splitting | 词分割 |
7 | pathname/filename expansion | 路径名扩展 |
8 | process substitution | 过程替换 |
2.1 brace expansion {}
大括号扩展。
有两种形式:
- pre + {str1,str2[,…,strN]} + post
- pre + {START..END[..INCR]} + post # 扩展成一个序列
使用注意事项:
- 这是单纯对字符串的扩展,即使不存在对应的文件名,也会扩展成功。
- 逗号两边都不允许有空格。
- 大括号与中间内容之间不允许在空格。
- 逗号前可以为空,表示待扩展的为空字符,见后面例子。
- 大括号可以嵌套使用,见后面例子。
- 大括号可以与其它扩展联用,见后面例子。
- START..END用于扩展为一个序列,一般用于循环。
- START..END支持逆序。
- START..END支持添加前导字符。
- START..END支持指定步长: START..END..INCR。
- START..END可以级联使用,类似于多重循环。
例子1: {str1,str2[,…,strN]}:
$ echo g{o,oo,ra}d
god good grad
$ cp a.log{,.bak} # 逗号前为空,扩展为
cp a.log a.log.bak
$ echo a{1, 2}b # 逗号两边有空格,不作为扩展处理,当作以空格分隔的两个str。
a{1, 2}b
$ echo {j{p,pe}, pn}g # 嵌套使用大括号
jpg jpeg png
$ echo {cat, d*} # 大括号与其它扩展联用
cat dog door # 注意,d*是文件名扩展,只有存在以d开头的文件名时才会扩展成功,
# 否则,会直接将d*当作变通str而不进行扩展。
例子2: {START..END[..INCR]}:
$ echo d{a..c}g # 普通范围扩展
dag dbg dcg
$ echo Num{5..1} # 逆序扩展
Num5 Num4 Num3 Num2 Num1
$ echo {08..12} # 添加前导0,将所有结果扩展成等宽的字符串
08 09 10 11 12
$ echo {08..17..2} # 对数字序列指定步长
08 10 12 14 16
$ echo {a..g..2} # 对字母序列也可以指定步长
a c e g
$ echo {x..z}{0..2}
x0 x1 x2 y0 y1 y2 z0 z1 z2
2.2 tilde expansion ~
波浪号扩展。
主要形式如下:
待扩展字符 | 扩展后字符 | 说明 |
---|---|---|
~ | /home/`whoami` | 当前用户的主目录 |
~username | /home/username | 特定用户的主目录 |
~+ | `pwd` | 当前路径, 相当于pwd |
~root | /root | /root目录 |
注意:
- ~到/之间的字符都称为tilde-prefix。
- 波浪号用于匹配目录,对于~username,如果username不存在该用户,则扩展不成功。按原样输出字符。
2.3. parameter and variable expansion
参数和变量扩展。
两个符号
符号 | 说明 |
---|---|
$ | 可用于参数和变量扩展,也可以用于命令替换和算术扩展。它是参数、变量、命令、算术扩展的“引导符” |
{} | 在参数(参数小于10时)和变量扩展中,{}不是必须的,但它可以起到保护变量的作用,当变量名后跟着其它字符时,用于区分变量名与字符。 |
注意:
- 使用!可以将变量的值又当作变量,多做一次扩展。
- 可以使用${VAR:=defaultvalue}的方式指定默认值
例子:普通变量扩展
$ NAME=LINUX
$ echo pre${NAME}post # 变量扩展,将${NAME}替换为LINUX。
preLINUXpost
$
$ echo pre$NAMEpost # 没使用{},认为$NAMEpost是一个变量,为空,所以扩展为pre。
pre
例子:使用!
$ NAME=LINUX
$ LINUX=Ubuntu
$ echo pre${NAME}post
preLINUXpost
$ echo pre${!NAME}post # 加一个!,将NAME的值LINUX又当作变量名,扩展为Ubuntu。
preUbuntupost
例子:自定义变量默认值
$ NAME= # 清空NAME的值,由于bash未定义的变量值都是空,所以直接赋值为空。
$ echo pre{NAME:=WINDOWS}post # NAME未定义,NAME赋值为默认值WINDOWS。
preWINDOWSpost
$ echo $NAME # 在上一语句中,NAME已经被赋值为指定的默认值
WINDOWS
2.4 arithmetic expansion
算术扩展。
语法: $((EXPRESSION))
注意:
- (())可以进行算术运算。
- (())可以将非10进制数转为10进制数。
- (())中可以有空格,也可以没空格。
- (())中可以使用变量。
例子:
$ echo $(( 3 + 4 ))
7
$ A=3
$ echo $(($A+4))
7
$ A=$((011)); echo $A # 以0开头的是8进制数
9
$ A=$((0x11)); echo $A # 以0x开头的是16进制数
17
2.5. command substitution
命令替换。
语法:$(command) 或 `command`
作用:将command的输出作为当前命令的一部分。
注意:
- $(command) 使用小括号,是命令替换,${VAR}使用大括号是变量扩展。$((expr))是算术扩展。
- $(command) 将command的输出作为当前命令,而不只是执行command
$ date
2020年 08月 12日 星期三 11:06:11 CST
$ $(date) # 本条命令相当于在命令行执行“2020年 08月 12日 星期三 11:06:11 CST”。
2020年: command not found
2.6 word splitting
词分离。
如果参数扩展、命令替换、算术扩展没有在双引号中,shell将对其结果进行“词分离”操作。
shell把$IFS中的每一个字符看做是分隔符。
默认情况下$IFS的值是“
可以修改$IFS的值。
$ IFS=’123’ # 1,2,3都作为词分隔符
$ echo a1b2c3d4 # 在命令行中,不进行“word splitting”操作
a1b2c3d4
$ echo $(echo a1b2c3d4) # 将echo a1b2c3d4放到命令替换语句中
a b c d4 # 以1,2,3将a1b2c3d4分割为a b c d4共4个份
$ ls $(echo a1b2c3d4) # 将echo a1b2c3d4放到命令替换语句中
ls: canot access ‘a’: No such file or directory # a b c d4放到ls命令后面
ls: canot access ‘b’: No such file or directory # 相当于执行 ls a b c d4
ls: canot access ‘c’: No such file or directory #
ls: canot access ‘d4’: No such file or directory#
2.7 filename expansion
文件名扩展.
全为3种
扩展名 | 说明 | 例 |
---|---|---|
* | 匹配0~N个任意字符, 不能匹配空格 | ls .txt, ls . |
? | 匹配1个任意字符,不能匹配空格 | ls ?.txt |
[abc] | 匹配a、b、c中任意一个字符 | ls [ab].txt |
[^abc]或[!abd] | 匹配a、b、c以外的任意一个字符 | ls [!ab].txt |
[start-end] | 匹配连续范围内的任意一个字符 | ls [a-c].txt |
[^start-end] | 匹配连续范围外的任意一个字符 | ls [^1-3].txt |
注意:
- 只有对应文件名存在时才能扩展成功,否则原样输出。比如 ls *.txt如果路径下没有.txt文件,则直接将 .txt作为ls的参数,会报告没有“.txt”这个文件。
- *和?不能匹配空格。
例子:
$ ls * # 所有文件,但不包含隐藏文件
$ ls .* # 所有隐藏文件,但包含了’.’和’..’这两个目录
$ ls .[!.]* # 所有隐藏文件,不包括’.’和’..’这两个目录
$ ls [a-c].txt # a.txt b.txt c.txt,只扩展为已经存在的文件。
# 比如如果b.txt不存在,则只扩展为a.txt c.txt
2.8. process substitution
过程替换。
过程替换与重定向比较容易搞混:重定向是一口入一口出,过程替换是多入(实际是并没有“入”)。看如下试验:
$ ls <(true) # 注意,”<”和”(“之间不能有空格,否则认为是重定向,语法错误。
/dev/fd/63 # 可以认为<(true)就是文件/dev/fd/63
$ vim <(ls) # 通过vim窗口底部显示,确认文件名为/dev/fd/63.
1 a.txt
2 c.txt
3 ex.sh
“/dev/fd/63” [readonly] 3L, 18C
$ ls <(true) >(true) # 可以认为<(LIST) 和 >(LIST)分别都是一个文件名。
/dev/fd/62 /dev/fd/63
if the “>(LIST)” form is used, writing to the file will provide input for LIST.
if the “<(LIST)” form is used, the file passed as an argument should be read to obtain the output of LIST
举例说明:
$ cat <(ls) # <(ls)是一个文件,文件内容是ls的结果,cat这个文件。
$ ls > >(cat) # >(cat)是一个文件,文件的内容是ls重定向来的,cat这个文件。
例子:将两个文件分别sort并排序,然后找出在两个文件中都出现的行。
$ cat a.txt
d3
d4
a0
a1
a2
$ cat b.txt
c0
c1
c2
d3
d4
$ comm <(sort -u a.txt) <(sort -u b.txt) # 给comm传两个文件,文件内容是sort -u结果。
a0 #---第一列,仅出现在第一个文件中的行
a1
a2
c0 #---第二列,仅出现在第二个文件中的行
c1
c2
d3 #---第三列,在两个文件中都出现的行
d4