在做项目的时候,往往需要shell脚本来运行linux语句,它是用户使用 Linux 的桥梁,今天我们通过这篇文章入门shell脚本。
1 shell介绍
shell就是俗称的壳,用于与用户交互,接收用户命令,调用相关程序。
可以将shell分为2大类
1.1 图形界面shell (Graphical User Interface shell即GUI shell)
也就是用户使用 GUI 和计算机核交互的 shell,比如 Windows 下使用最广泛的 Windows Explorer(Windows 资源管理器),Linux 下的 X Window,以及各种更强大的 CDE、GNOME、KDE、 XFCE。他们都是GUI shell。
1.2 命令行式 shell(Command Line Interface shell ,即 CLI shell)
也就是通过命令行和计算机交互的 shell。Windows NT 系统下有 cmd.exe(命令提示字符)和近年来微软大力推广的 Windows PowerShell。Linux 下有 bash / sh / ksh / csh/zsh 等 一般情况下,习惯把命令行 shell(CLI shell)直接称做 shell。
2 交互方式
根据交互方式的不一样,命令行式 shell(CLI shell),又分为交互式 shell 和非交互式 shell。
2.1 交互式shell
交互式模式就是 shell 等待你的输入,并且执行你提交的命令,然后马上给你反馈。这种也是我们大多数时候使用的。
2.2 非交互式shell
非交互式 shell,就是把 shell 放在写在一个文件里面,执行的时候,不与用户交互,从前往后依次执行,执行到文件结尾时,shell 也就终止了。
3 shell种类
在 Linux 下 ,各种 shell 百花齐放,种类繁多,不同的 shell,也有不同的优缺点。我们要查看当前系统下支持的 shell,可以读取 / etc/shells 文件。
4 shell脚本
4.1 基础
#!/bin/bash
echo "Hello World!"
(#!)是个特殊的标记,表明用哪个解释器执行。例如这里用到了/bin/bash来执行shell脚本
执行并获取返回结果,有点类似 JavaScript 的 eval 函数。
#!/bin/bash
dt=`date` #反引号内的字符串会当作shell执行 ,并且返回结果。
echo "dt=${dt}"
4.2 Shell变量
shell 的使用比较简单,没有数据类型的概念,所有的变量都可以当成字符串来处理:
使用变量
ABC="tom"
echo $ABC #使用变量前面加$美元符号
echo "ABC=$ABC" #可以直接在字符串里面引用
echo "ABC=${ABC}" #但是建议把变量名字用{}包起来
只读变量
ABC="tom"
echo "ABC=${ABC}"
readOnly ABC #设置只读
ABC="CAT" #会报错,因为设置了只读,不能修改
删除变量
ABC="tom"
echo "ABC=${ABC}"
unset ABC #删除
echo "ABC=$ABC"
echo "ABC=${ABC}"
使用一个不存在的变量不会报错,只是当作空字符串处理
4.3 Shell字符串
使用字符串
A=my #你甚至可以不用引号,但是字符串当中不能有空格,这种方式也不推荐
B='my name is ${NAME}' #变量不会被解析
C="my name is ${NAME}" #变量会解析
echo $A
echo $B
echo $C
我们可以发现,这个字符串的单双号和 PHP 的处理非常类似,单引号不解析变量,双引号可以解析变量。
拼接字符串
其实 shell 拼接字符串,大概就是 2 种
- 直接在双引号内应用变量,类似模板字符串
- 直接将字符串写在一起
# 使用双引号拼接
echo "hello, "$NAME" !" #直接写在一起,没有字符串连接符
echo "hello, ${NAME} !" #填充模版
# 使用单引号拼接
echo 'hello, '$NAME' !' #直接写在一起,没有字符串连接符
echo 'hello, ${NAME} !' #上面已经提高过,单引号里面的变量是不会解析的
强大的字符串处理 shell 中简单的处理字符串,可以直接使用各种标记,只是比较难记忆,要用的时候,可以查一下。
ABC="my name is tom,his name is cat"
echo "字符串长度=${#ABC}" # 取字符串长度
echo "截取=${ABC:11}" # 截取字符串, 从11开始到结束
echo "截取=${ABC:11:3}" # 截取字符串, 从11开始3个字符串
echo "默认值=${XXX-default}" #如果XXX不存在,默认值是default
echo "默认值=${XXX-$ABC}" #如果XXX不存在,默认值是变量ABC
echo "从开头删除最短匹配=${ABC#my}" # 从开头删除 my 匹配的最短字符串
echo "从开头删除最长匹配=${ABC##my*tom}" # 从开头删除 my 匹配的最长字符串
echo "从结尾删除最短匹配=${ABC%cat}" # 从结尾删除 cat 匹配的最短字符串
echo "从结尾删除最长匹配=${ABC%%,*t}" # 从结尾删除 ,*t 匹配的最长字符串
echo "替换第一个=${ABC/is/are}" #替换第一个is
echo "替换所有=${ABC//is/are}" #替换所有的is
4.4 数组
与大多数语言一样从0下标开始,但是是用空格分隔开来的,就像下面这样
array=("items1","items2","items3")
也可以根据下标定义元素
array[0]="new_item0"
array[1]="new_item1"
array[2]="new_item2"
array[4]="new_item4" #数组下标可以是不连续的
读取数组元素,和变量类似
echo ${array[0]}
echo "array[0]=${array[0]}"
获得所有的数组
echo "数组的元素为: ${array[*]}"
echo "数组的元素为: ${array[@]}"
获得数组的长度
echo "数组的长度为: ${#array[*]}"
echo "数组的长度为: ${#array[@]}"
4.5 输入输出
4.5.1 echo
加入特殊符号,可以用来转义
echo "Hello \nworld!"
echo "\"Hello\""
echo '"Hello"' #当然,也可以这样,单引号不转义,上文提到过
echo `date` #打印执行date的结果,注意,单引号不转义
echo -n "123" #加-n 表示不在末尾输出换行
echo "456"
echo -e "\a处理特殊符号" #-e 处理特殊符号
符号 | 作用 |
---|---|
\a | 发出警告声 |
\b | 删除前一个字符 |
\c | 后面不加上换行符 |
\f | 换行但光标仍旧停留在原来的位置 |
\n | 换行且光标移至行首 |
\r | 光标移至行首,但不换行 |
\t | 插入tab |
4.5.2 read
有出必有入,下面介绍输入
read name
echo "my name is ${name}"
可以输入-p给定提示
read -p "please input your name:" name
echo "my name is ${name}"
如果没有给定变量,会给环境变量REPLY赋值
read -p "please enter your name:"
echo "your name is ${REPLY}"
指定时间内输入
read -t 3 -p "please enter your name:"
echo "your name is ${REPLY}"
指定输入字符个数
read -n 1 -p "are you sure Y/S"
默读(输入不在监视器显示)
read -s -p "please enter your name:"
4.5.3 printf
类似于C语言的printf,用于字符串模板输出
printf "%-10s %-8s %-4s\n" 姓名 性别 体重kg
printf "%-10s %-8s %-4.2f\n" 张三 m 123
printf "%-10s %-8s %-4.2f\n" 李四 f 213
printf "%-10s %-8s %-4.2f\n" 王五 m 534
%s %c %d %f 都是格式替代符 %-10s 指一个宽度为 10 个字符(- 表示左对齐,没有则表示右对齐),至少显示 10 字符宽度,如果不足则自动以空格填充,超过不限制。%-4.2f 指格式化为小数,其中. 2 指保留 2 位小数。
4.5.4 重定向
大多数 UNIX 系统命令从你的终端接受输入并将所产生的输出发送回到您的终端
命令 | 作用 |
---|---|
command > file | 将输出重定向到 file |
command < file | 将输入重定向到 file |
command >> file | 将输出以追加的方式重定向到 file |
n > file | 将文件描述符为 n 的文件重定向到 file |
n >> file | 将文件描述符为 n 的文件以追加的方式重定向到 file |
n >& m | 将输出文件 m 和 n 合并 |
n <& m | 将输入文件 m 和 n 合并 |
<< tag | 将开始标记 tag 和结束标记 tag 之间的内容作为输入 |
需要注意的是文件描述符 0 通常是标准输入(STDIN),1 是标准输出(STDOUT),2 是标准错误输出(STDERR)。
输出文件
echo "test">text.txt #直接输出
echo "test">>text.txt #追加在text.txt后面
4.6 条件判断语句
单分支
if condition
then
command1
command2
...
fi
双分支
if condition
then
command1
command2
else
command3
...
fi
多分支
if condition1
then
command1
command2
elif condition2
then
command3
else
command4
...
fi
比如
if [ "2" == "2" ];
then
echo "2==2"
else
echo "2!=2"
fi
特别注意[ "2" == "2" ]两边都有等号,否则会报错
判断文件是否存在
if [ -f "1.sh" ]; then # 判断一个普通文件是否存在
echo "1.sh 存在"
fi
判断字符串长度=0
a=""
if [ -z $a ]; then
echo "a为空"
fi
4.7 shell中的括号()、(())、[]、[[]] 和 {}
4.7.1. 单小括号()
- 命令组括号中的命令会新开一个shell顺序执行,所以括号中的变量不能被另外的括号使用
a="123"
(echo "123";a="456";echo "a=¨E61Ea")
- 命令替换 发现$(cmd) 结构,便将 $(cmd) 中的 cmd 执行一次,得到其标准输出,再将此输出放到原来命令。
- 用于初始化数组 如:array=(a b c d)
4.7.2 双小括号(())
- 运算扩展,比如,你可以
a=$((4+5))
echo "a=$a"
- 做数值运算,重新定义变量
a=5
((a++))
echo "a=$a"
- 用于算术运算比较
if ((1+1>1));then
echo "1+1>1"
fi
4.7.3 单中括号[]
- 用于字符串的比较,运算符只能是==和!=,而且两边都要有空格
if [ "2" == "2" ]; then # "2" 的2边都有空格,不能省略
echo "2==2"
else
echo "2!=2"
fi
- 用于整数比较 需要注意,整数比较,只能用 - eq,-gt 这种形式,不能直接使用大于 (>) 小于 (<) 符号。只能用于整形
if [ "2" == "2" ]; then # "2" 的2边都有空格,不能省略
echo "2==2"
else
echo "2!=2"
fi
符号表(只能用于整型)
符号 | 运算 |
---|---|
-eq | 等于(equal) |
-ne | 不等于(not equal) |
-gt | 大于(great than) |
-ge | 大于等于(great equal) |
-lt | 小于(low than) |
-le | 小于等于(low equal) |
- 多个逻辑组合 -a 表示 and 与运算 -o 表示 or 或运算
if [ "2" == "2" -a "1" == "1" ]; then #注意,在这里,不能是[ "2" == "2" ] -a [ "1" == "1" ] 会报错
echo "ok"
fi
4.7.4 双中括号[[]]
[[]]表示的是程序中的关键字
- 字符串匹配支持正则表达式
if [[ "123" == 12* ]]; then #右边是正则不需要引号
echo "ok"
fi
- 支持对数字的判断,是支持浮点型的,并且可以直接使用 <、>、==、!= 符号
if [[ 2.1 > 1.1 ]]; then
echo "ok"
fi
- 多个逻辑判断 可以直接使用 &&、|| 做逻辑运算,并且可以在多个 [[]] 之间进行运算
if [[ 1.1 > 1.1 ]] || [[ 1.1 == 1.1 ]]; then
echo "ok"
fi
4.7.5 大括号{}
创建新的文件
touch new_{1..5}.txt #创建new_1.txt new_2.txt new_3.txt new_4.txt new_5.txt 5个文件
4.8 循环
4.8.1 for循环
语法格式为
for a in "item1" "item2" "item3"
do
echo $a
done
输出当前目录为.sh结尾的文件
for a in `ls ./`
do
if [[ $a == *.sh ]]
then
echo $a
fi
done
4.8.2 while循环
输出1-100的数
int=1;
while(($int<=100))
do
echo $int
((int++))
done
4.8.3 until循环
语法
until condition
do
command
done
循环中的continue和break与其它语言类似
4.9 case
case 和其他语言 switch 类型,多分支,选择一个匹配。匹配发现取值符合某一模式后,其间所有命令开始执行直至 ;;,有点类型 Java 的 break。如果无一匹配模式,使用星号 * 捕获该值,再执行后面的命令。
echo '输入 1 到 4 之间的数字:'
echo '你输入的数字为:'
read aNum
case $aNum in
1) echo '你选择了 1'
;;
2) echo '你选择了 2'
;;
3) echo '你选择了 3'
;;
4) echo '你选择了 4'
;;
*) echo '你没有输入 1 到 4 之间的数字'
;;
esac
4.10 函数
shell 也可以用户定义函数,然后在 shell 脚本中可以随便调用。注意:所有函数在使用前必须定义。这意味着必须将函数放在脚本开始部分,直至 shell 解释器首次发现它时,才可以使用。调用函数仅使用其函数名即可。语法格式如下:
[function] funname()
{
cmd....
[return int]
}
一个最简单的函数
Line(){
echo "--------分割线--------"
}
echo "123"
Line
echo "456"
在 Shell 中,调用函数时可以向其传递参数。在函数体内部,通过 1表示第一个参数,$2 表示第二个参数… 调用的时候 ,函数名,参数直接用空格分割开。带参数的函数示例:
out(){
echo "1-->$1"
echo "2-->$2"[图片上传中...(参数.png-981b9a-1577263405886-0)]
}
out 1 2 #调用的之后
还有一些特殊的符号需要注意
符号 | 作用 |
---|---|
$# | 传递到脚本的参数个数 |
$* | 以一个单字符串显示所有向脚本传递的参数 |
$$ | 脚本运行的当前进程 ID 号 |
$@ | 与 $* 相同,但是使用时加引号,并在引号中返回每个参数 |
$? | 显示最后命令的退出状态。0 表示没有错误,其他任何值表明有错误 |
下面是一个返回所有值的例子
out(){
echo "全部参数$*"
for item in $*
do
echo "$item"
done
return $# #这类返回参数个数,返回值必须是整数
}
out this is perfect
echo "函数返回值:$?"
5 其他实用技巧
shell 脚本,他本身的功能并不强大,强大的是他可以调用其他程序,而在 Linux 下,系统自带的就有非常多的强大工具可以调用。
5.1 后台执行
后台执行一个脚本只需要在后面加上 & 符号即可,我们先用之前学习的,写一个脚本,1s 输出一个数字
#!/bin/bash
i=1
while :
do
echo $i
i=$(( $i + 1 ))
sleep 1s #sleep one second
done
我们执行 sh d.sh & 我们发现,的确会后台输出,但是会输出到当前控制台,我们可以用之前学的重定向,把输出重定向到文件
sh d.sh > out.log 2>&1 &
sh d.sh > out.log 意思是将d.sh输出重定向到out.log,2>&1 &的意思是将错误信号(2表示)也作为输入信号(1表示)重定向。但是我们会发现,如果关闭终端,会产生SIGHUP信号,导致程序退出,所以我们的程序用到nuhup达到真正的后台。
nuhup sh d.sh > out.log 2>&1 &
这样就可以达到真正的后台了。那么,我们如何验证程序在后台运行呢?要怎么结束后台。
5.2 tail
查看文件的后面几行tail out.log,默认输出10行,也可以指定行数,比如tail -n 5 out.log,也可以用tail -f out.log,若有更新则输出结果。
5.3 ps
ps,查询进程,一般查询,就用ps u就行了,显示面向用户格式的后台进程信息。
6 总结
- shell 使用的比较少,但是特别强大
- 使用到的编号、编码、参数特别多,并且都是简写,很多记不住。其实不用死记硬背,记住有这个功能就可以了,需要用到的时候再查询。