shell
简介
Shell是一种脚本语言,通常用于在Unix和类Unix操作系统上进行命令行交互。它是用户与操作系统内核之间的接口,允许用户通过命令行或脚本文件来控制计算机上的操作。Shell程序通常用于执行复杂的任务,例如文件操作、网络通信、进程控制等。
Shell语言具有很高的灵活性,可以根据需要编写自定义脚本来自动化任务。Shell脚本通常包含一系列命令和流程控制语句,可以执行文件操作、程序运行、环境变量设置等任务。在Unix和类Unix操作系统中,常见的Shell包括Bash、Csh、Ksh等,它们都具有各自的特点和用法。Linux中默认使用bash。
运行
-
文件开头需写
#! /bin/bash
,指明默认的脚本解释器。 -
运行方式:
-
用解释器执行(可以没有权限):
bash test.sh
,test.sh是Shell脚本文件的名称。 -
作为可执行文件执行(必须要有权限):
chmod +x test.sh
,使脚本具有可执行权限。然后./test.sh
直接运行。 -
在终端中执行Shell命令,并执行脚本:
echo "echo Hello, World!" | sh
其中第一种方法较常用。
-
语法
注释
-
单行注释(
#
注释)
在Shell脚本中,一行代码以#
开头时,该行代码就被认为是注释,Shell 解释器会将这一行代码忽略。例如:#! /bin/bash # This is a comment echo "Hello World!"
-
多行注释
在Shell脚本中,我们也可以使用一对: << EOF
和EOF
来实现多行注释,其中一对EOF可以换成其他字符串,如: << qingqiu
qingqiu
。#! /bin/bash : << suibian 第一行注释 第二行注释 第三行注释 suibian echo "Hello, World!"
变量
变量的命名规则
在Shell中,变量名遵循以下命名规则:
-
变量名必须以字母或下划线开头。
-
变量名中只能包含字母、数字和下划线。
-
变量名不能以数字开头。
-
变量名区分大小写。
-
变量名和变量值之间不能有空格。
-
如果变量值包含空格,需要使用引号将变量值括起来,例如
name="qing qiu"
。
处理变量
在Shell中,变量可以用来存储字符串或数字等信息。可以通过定义变量、给变量赋值、使用变量等操作来进行变量处理。
-
定义变量
定义变量有三种方法- name=qingqiu
- name=“qingqiu”
- name=‘qingqiu’
其中不加引号和加双引号效果相同,支持变量替换和转义字符的解析,例如:
name="qingqiu" echo "My name is $name" # 输出:My name is qingqiu echo My name is $name # 输出:My name is qingqiu
而单引号不支持变量替换和转义字符的解析,在单引号中,所有的字符都会被当作普通字符处理,例如:
name="qingqiu" echo 'My name is $name' # 输出:My name is $name
-
给变量赋值
可以使用=号来给变量赋值,等号两边不能有空格,否则会报错。
-
使用变量
使用变量时需要在变量名前加上$,或者${},{}
用于识别变量边界。例如:name=qingqiu echo $name # 输出qingqiu echo ${name} # 输出qingqiu echo $nameye # 输出nameye echo ${name}ye # 输出qingqiuye
-
只读变量
readonly
或者declare
可以将变量变为只读,修改只读变量时会报错。name=qingqiu readonly name declare -r name # 两种写法都可以 name=a # 会报错,因为此时name只读
取消只读属性:
unset -v name
-
删除变量
unset
可以删除变量。name=qingqiu unset name echo $name # 输出空行
要想删除只读变量,需要先取消只读属性,再用
unset
删除。
变量类型
局部变量与全局变量
- 自定义变量(局部变量)
子进程不能访问的变量。 - 环境变量(全局变量)
子进程可以访问的变量。
自定义变量与环境变量可以互相转换:
自定义变量→环境变量
name=qingqiu
export name # 方法一
declare -x name # 方法二
环境变量→自定义变量
export name=qingqiu # 定义环境变量,只在当前bash有效
declare +x name # 改为自定义变量
字符串
在shell中,字符串可以用单引号、双引号或不用引号来表示。单引号与双引号有区别,参考上述定义变量处。
字符串的操作
-
获取字符串长度
name="qingqiu" echo ${#name} # 输出7
-
提取子串
name="qingqiu" echo ${name:0:4} # 提取从0开始的4个字符,输出qing
数字
数字变量用来存储数字类型的数据。在shell中,数字变量可以是整数或浮点数。
数组
数组中可以存放多个不同类型的值,下标从0开始,初始化时不需要指明数组大小。
- 定义
-
使用圆括号括起数组元素,并用空格隔开。例如:
array=(666 qing "qiu" haha)
-
使用花括号括起数组元素,并用逗号隔开。例如
array={666, qing, "qiu", haha}
-
直接定义数组中的某个元素的值:
array[0]=666 array[1]=qing array[2]="qiu" # 只输出引号内字符串,建议使用"",以避免字符串含空格时出错 array[3]=haha
直接定义某个元素的值时,下标可以不连续。例如:
array[0]=666
array[6]=qing
array[9]=“qiu”
array[999]=haha
echo ${#array[@]} # 输出4
-
读取某个元素的值
${array[index]} # 格式 echo ${array[0]} # 输出666 echo ${array[1]} # 输出qing echo ${array[2]} # 输出qiu echo ${array[3]} # 输出haha
-
读取整个数组
${array[@]} ${array[*]} # 两种格式 echo ${array[@]} echo ${array[*]} # 两种写法均可
在读取整个数组时,未定义的部分不会显示。例如:
array[0]=666 array[6]=qing array[9]=qiu array[999]=haha echo ${array[@]} # 只会输出以上定义的四个值
-
数组长度
${#array[@]} ${#array[*]} # 两种格式 echo ${#array[@]} echo ${#array[*]} # 两种写法均可
默认变量
默认变量是指在Shell中已经定义好的一些特殊变量,可以在Shell脚本中直接使用。一些常用的默认变量如下:
变量 | 说明 |
---|---|
$0 | 当前脚本的文件名 |
$1 ~ $9 | 传递给脚本或函数的参数。其中,$1 表示第一个参数,$2 表示第二个参数,以此类推。如果>9,需要用{}括起来,例如:${10} |
$# | 传递给脚本或函数的参数个数 |
$* | 以一个单字符串显示所有向脚本传递的参数。参数之间用空格分隔 |
$@ | 与$* 类似,但是每个参数都是独立的字符串,可以避免参数中含有空格等特殊字符的问题 |
$? | 上一个命令或者函数的退出状态exit code ,0表示成功,非0表示失败 |
$$ | 当前Shell进程的进程号 |
$(command) | 返回command 这条命令的标准输出stdout (可嵌套) |
command | 返回command 这条命令的标准输出stdout (不可嵌套) |
注意区分exit code
与stdout
:exit code用于表示命令或程序的执行结果,而stdout则是命令或程序输出的内容。
expr命令
expr 表达式
用于求表达式的值。
说明
- 用空格隔开每一项。
- 使用shell特定的字符需转义,或用反引号``(fn+~)括起来。
- 对包含空格和其他特殊字符的字符串要用引号括起来。
expr
会在stdout
中输出结果。如果为逻辑关系表达式,则结果为真时,stdout
输出1,否则输出0。1为真,0为假expr
同时会在exit code
中输出结果。如果为逻辑关系表达式,则结果为真时,exit code
为0,否则为1。0为真,1为假
字符串表达式
-
length string
返回string
的长度 -
index string charset
charset
中任意单个字符在string
中最前面的字符位置,下标从1开始。如果string
中完全不存在charset
中的字符,则返回0。string="qingqiu" echo ${expr index "$string" ig} # i在第2个位置出现,输出2 echo ${expr index "$string" axx} # 任一字符都没出现,输出0
-
substr string position length
返回string
字符串中从第position
个位置开始,长度最大为length
的子串。如果position
或length
为0、负数、或非数值,则返回空字符串。
str="Hello World!"
echo `expr length "$str"` # 输出12。反引号``表示直接获取命令的标准输出
echo `expr index "$str" ala` # 输出3
echo `expr substr "$str" 2 3` # 输出ell
整数表达式
expr
支持普通算数操作,算术表达式优先级低于字符串表达式,高于逻辑关系表达式。
+ -
加减运算。两端参数回转换成整数,如果转换失败则报错。* / %
乘、除、取模运算,*
需要转义。两端参数会转换为整数,如果转换失败则报错。()
可以改变优先级,但使用时需要转义。
a=5
b=6
echo `expr $a + $b` # 输出11
echo `expr $a / $b` # 输出0
echo `expr \( $a + 1 \) \* \( $b + 4 \)`
逻辑表达式
|
如果第一个参数非空且非0,则返回第一个参数的值,否则返回第二个参数的值,但要求第二个参数的值也是非空或非0,否则返回0。如果第一个参数是非空或非0时,不会计算第二个参数。&
如果两个参数都非空且非0,则返回第一个参数,否则返回0。如果第一个参为0或为空,则不会计算第二个参数。
|和&
-
< <= = == != >= >
比较两端的参数,如果为true,则返回1,否则返回0。==
与=
是同义词。expr
首先尝试将两端参数转换为整数,并做算术比较,如果转换失败,则按字符集排序规则做字符比较。 -
()
可以改变优先级,但需要转义。
read命令
read
命令用于从标准输入中读取单行数据。当读到文件结束符时,exit code
为1,否则为0。
参数说明
-p
:后面可以接提示信息。-t
:后面跟秒数,定义输入字符的等待时间,超过等待时间后会自动忽略此命令。
read name
echo $name
read -p "Please input: " -t 30 name # 读入name的值,等待时间为30s
echo命令
echo
用于输出字符串。
-
显示普通字符串
echo "qingqiu" echo qingiu # ""可以省略
-
显示转义字符
echo "\"qingqiu\"" echo \"qingqiu\" # “”带不带都可以,但不能带‘’
-
原样输出字符
name=qingqiu echo '$name\"' # ‘’内的内容原样输出,输出$name\"
-
显示变量
name=qingqiu echo "I'm $name" # 输出I'm qingqiu
-
显示换行
echo -e "hello\n" # -e开启转义,\n表示hello后换行
关于
-e
开启转义:在shell脚本中,可以使用-e
选项来开启转义字符的解析,具体来说,-e
选项可以用于以下几种情况:- 转义字符的解析:当开启
-e
选项时,shell会将命令行中的转义字符进行解析,例如:\n
会被解析成换行符,\t
会被解析成制表符等。 - 强制文件名展开:在某些情况下,shell会对文件名进行展开,例如:
ls *.txt
,这个命令会将当前目录下所有的txt文件列出来。但是,如果有一个文件名包含了特殊字符,例如空格,那么这个文件名就无法被正确展开。此时可以使用-e
选项来开启转义,例如:ls -e "my file.txt"
,这样可以强制shell将文件名中的空格转义,从而正确展开文件名。 - 解析
echo
命令中的转义字符:在echo
命令中,可以使用转义字符来输出一些特殊字符,例如:\n
可以输出换行符,\t
可以输出制表符等。但是,在某些系统上,echo
命令默认不解析转义字符,因此需要使用-e
选项来开启转义,例如:echo -e "hello\nworld"
,这样可以正确输出换行符。
需要注意的是,在使用
-e
选项时,要特别小心一些特殊字符的转义,例如\$
符号,因为它会影响变量的展开。如果要输出\$
符号,可以使用转义字符。 - 转义字符的解析:当开启
-
显示不换行
echo -e “hello\c” # -e开启转义,\c表示hello后不换行
-
显示结果定向至文件
echo "qingqiu" > output.txt # 将内容以覆盖方式输出到outpu.txt中
-
显示命令执行结果
echo `date` # ``是反引号,输出现在的时间
printf命令
用于格式化输出,类似于C/C++
中的printf
函数。
test命令与判断符号[]
逻辑运算符
&&
表示与,||
表示或。exit code
为0时表示真,非零时表示假。
tips
-
|
和&
是expr
定义的,||
和&&
是bash自带的。 -
||
和&&
可以判断表达式是否为真,但根据短路原则,也常常用于实现if
的作用。
短路原则:expr1 && expr2
:当expr1
为假时,直接忽略expr2
。expr1 || expr2
:当expr1
为真时,直接忽略expr2
。# 假设当前目录下有一个文件test.sh test -e test.sh && echo "exist" || echo "not exist" # 若test.sh存在,输出exist,否则输出not exist
test命令
test
命令用于判断文件类型以及比较变量,用exit code
返回结果,0为真,非0为假。(expr
用stdout
返回结果,1表示真,0表示假)
-
判断文件类型
test -e filename # 判断文件是否存在 test -f filename # 判断是否为文件 test -d filename # 判断是否为目录
-
判断文件权限
test -r # 判断文件是否可读 test -w # 判断文件是否可写 test -x # 判断文件是否可执行 test -s # 判断是否为空文件
-
比较整数
test $a -eq $b # 判断a是否等于b test $a -ne $b # 判断a是否不等于b test $a -gt $b # 判断a是否大于b test $a -lt $b # 判断a是否小于b test $a -ge $b # 判断a是否大于等于b test $a -le $b # 判断a是否小于等于b
-
比较字符串
test -z string # 判断string是否为空,若为空返回true test -n string # 判断string是否非空,若非空返回true(-n可以省略) test str1 == str2 # 判断str1是否等于str2 test str1 != str2 # 判断str1是否不等于str2
-
多重条件判定
test -r filename -a -x filename # (and) 两个条件是否同时成立 test -r filename -o -x filename # (or) 两个条件是否至少成立一个 test ! -x file # (非) 取反。file不可执行时返回true
判断符号[](命令)
[]
与test
用法几乎一模一样,更常用于if
语句中。另外[[]]
是[]
的加强版,支持的特性更多。
tips:
[]
中只有[
是命令,]
可以理解为一个参数。在使用时二者缺一不可。
注意:
[]
是一个命令,里面的每一项都要用空格隔开- 中括号内的变量,最好用双引号括起来
- 中括号内的常数,最好用单或双引号括起来
判断语句
if…then
类似于C/C++
中的if-else
语句。
-
单层if
# 格式 if 表达式 # 判断的是表达式的exit code # 注意if后应该接命令,而不能是一个值 then 语句1 ... fi # 示例 a=3 b=4 if [ "$a" -lt "$b" ] && [ "$a" -gt 2 ] then echo ${a}在范围内 fi # 输出3在范围内
-
单层if-else
# 格式 if 表达式 then 语句1 ... else 语句1 ... fi # 示例 a=3 b=4 if [ "$a" -lt "$b" ] then echo ${a}小于${b} else echo ${a}不小于${b} fi # 输出3小于4
-
多层if-elif-elif-else
# 格式 if 表达式 then 语句1 ... elif condition then 语句1 ... elif condition then 语句1 else 语句1 ... fi # 示例 a=4 if [ $a -eq 1 ] then echo ${a}等于1 elif [ $a -eq 2 ] then echo ${a}等于2 elif [ $a -eq 3 ] then echo ${a}等于3 else echo 其他 fi # 输出其他
case…esac
类似于C/C++
中的switch
语句。
# 格式
case $变量名 in
值1)
语句1
...
;; # 类似于C/C++中的break
值2)
语句1
...
;;
*) # 类似于C/C++中的default(匹配所有值)
语句1
...
;;
esac
# 示例
a=4
case $a in
1)
echo ${a}等于1
;;
2)
echo ${a}等于2
;;
3)
echo ${a}等于3
;;
*)
echo 其他
;;
esac # 输出其他
循环语句
for…in…do…done
# 格式
for var in val1 val2 val3
do
语句1
...
done
# 示例1
for i in q i n g
do
echo $i
done # 每行输出一个元素
# 示例2
for file in `ls`
do
echo $file
done # 逐行输出当前路径下所有文件名
# 示例3
for i in $(seq 1 10) # seq命令返回一个序列
do
echo $i
done # 输出1-10
# 示例4
for i in {a..z} # bash内嵌的范围写法
do
echo $i
done # 逐行输出a,b,c,d,...,z
for ((…;…;…)) do…done
类似于C/C++
中的for
循环。
# 格式
for ((赋初值; 判断; 修改变量))
do
语句1
...
done
# 示例
for((i=1; i<=10; i++))
do
echo $i
done # 输出1-10
while…do…done
# 格式
while condition
do
语句1
...
done
# 示例1
i=0
while [ $i -lt 10 ]
do
echo $i
i=$(expr $i + 1) # 可以写成((i++))
done
# 示例2
while read name
do
echo $name # 读到结束符时,输出读取的字符串
done
until…do…done
条件为真时结束。
# 格式
until condition
do
语句1
...
done
# 示例
until [ "${word}" == "yes" ] || [ "${word}" == "YES" ] # 注意有两个[],用||隔开
do
read -p "Please input yes/YES to stop:" word
done # 输入yes/YES时结束,否则一直等待读入
跳出循环
-
break命令
跳出当前这层循环,
break
不能跳出case
语句,case
中的跳出循环用;;
。while read name do for ((i=1;i<=10;i++)) do case $i in 8) break ;; *) echo $i ;; esac done done # 每读入非EOF的字符串,会输出一遍1-7
-
continue命令
跳出当前这次循环。for ((i=1;i<=10;i++)) do if [ `expr $i % 2` -eq 0 ] then continue fi echo $i done # 输出1-10中所有奇数
死循环处理
- 使用
top
命令找到该进程的PID。 - 输入
kill -9 PID
关掉此进程。
函数
bash
中的函数类似于C/C++
中的函数,但return
的返回值与C/C++
不同,返回的是exit code
,取值为0-255,0表示正常结束。
如果想获取函数的输出结果,可以通过echo
输出到stdout
中,然后通过$(function_name)
来获取stdout
中的结果。
函数的return
值可以通过$?
来获取。
func()
{
name=qingqiu
echo "Hello $name"
return 9 # 不写return时默认return0
}
func # 调用时不需要加()
output=$(func) # 获取标准输出
ret=$? # 获取退出状态
echo "output = $output"
echo "return = $ret"
exit命令
-
exit
命令用来退出当前shell进程,并返回一个退出状态;使用$?
可以接收这个退出状态。 -
exit
命令可以接受一个整数值作为参数,代表退出状态。如果不指定,默认状态值是 0。 -
exit
退出状态只能是一个介于 0~255 之间的整数,其中只有 0 表示成功,其它值都表示失败。
文件重定向
每个进程默认打开3个文件:
stdin
:标准输入,从命令行读取数据,文件描述符为0stdout
:标准输出,向命令行输出数据,文件描述符为1stderr
:标准错误输出,向命令行输出数据,文件描述符为2
可以用文件重定向将这三个文件重定向到其他文件中。
命令 | 说明 |
---|---|
command > file | 将stdout重定向到file中(覆盖内容) |
command < file | 将stdin重定向到file中 |
command >> file | 将stdout以追加方式重定向到file中 |
command n> file | 将文件描述符n重定向到file中 |
command n>> file | 将文件描述符n以追加方式重定向到file中 |
引入外部脚本
类似于C/C++
中的include
操作,bash
也可以引入其他文件中的代码。
语法格式:
. filename # .和filename之间有一个空格
source filename # 两种方法都可以