[转帖]学习如何编写 Shell 脚本(基础篇)
https://juejin.cn/post/6930013333454061575
前言
如果仅仅会 Linux 一些命令,其实已经可以让你在平时的工作中游刃有余了。但如果你还会编写 Shell 脚本(尤其是前端工程师),它会令你“添光加彩”。
如果本文对你有所帮助,请点个 吧。
Shell 是什么
Shell可以是一个程序,提供一个与用户对话的环境。这个环境只有一个命令提示符,让用户从键盘输入命令,所以又称为命令行环境(command line interface,简写为CLI)。Shell接收到用户输入的命令,将命令送入操作系统执行,并将结果返回给用户。- Shell 也可以是一个命令解释器,解释用户输入的命令。它支持变量、条件判断、循环操作等语法,所以用户可以用
Shell命令写出各种小程序,又称为脚本script。这些脚本都通过Shell的解释执行,而不通过编译。 Shell还可以是一个工具箱,提供了各种小工具,供用户方便地使用操作系统的功能。
Shell 不像 C 语言, C++、Java 等编程语言那么完整,但是 Shell 这门语言可以帮助我们完成很多自动化任务,例如:保存数据,监测系统的负载,等等。Shell 相比 C 等语言的优势在于它是完全嵌入在 Linux 中的,不需要安装,编译。
下图表示 Shell 与内核、操作系统之间的关系:
Shell 的种类
Shell 有很多种,只要能给用户提供命令行环境的程序,都可以看作是 Shell 。
历史上,主要的 Shell 有下面这些:
Bourne Shell(sh),是目前所有Shell的祖先,被安装在几乎所有发源于Unix的操作系统上。Bourne Again shell(bash),是sh的一个进阶版本,比sh更优秀,bash是目前大多数Linux发行版以及macOS操作系统的默认Shell。C Shell(csh),它的语法类似C语言。TENEX C Shell(tcsh),它是csh的优化版本。Korn shell(ksh),一般在收费的Unix版本上比较多见。Z Shell(zsh),它是一种比较新近的Shell,集bash、ksh和tcsh各家之大成。
它们之间的演化关系图:
关于 Shell 的几个常见命令:
echo $SHELL可以查看本机正在使用的Shell,其中$SHELL是环境变量。cat /etc/shells可以查看当前的Linux系统安装的所有Shell。- 执行
chsh命令是切换Shell类型为chsh。
什么是 Shell 脚本
计算机脚本程序是确定的一系列控制计算机进行运算操作动作的组合,在其中可以实现一定的逻辑分支等。
通俗的理解就是,多个 Shell 命令的组合并通过 if 条件分支控制或循环来组合运算,实现一些复杂功能。
例如我们常用的 ls 命令,它本身也是一个 Shell 脚本,通过执行这个 Shell 脚本可以列举当前目录下的文件列表。
本文使用的 Shell 种类是 Bash 。
编写我们的第一个 Shell 脚本 hello.sh
#!/bin/bash
# 执行的命令主体
ls
echo "hello world"
复制代码
#!/bin/bash指定脚本要使用的Shell类型为Bash。#!被称为Sha-bang,或者Shebang,Linux会分析它之后的指令,并载入该指令作为解析器。ls就是脚本文件的内容了,表明我们执行hello.sh脚本时会列举出当前目录的文件列表并且会向控制台打印hello world。
如果我们不是 root 用户的话,需要给脚本添加可执行权限才可以运行, chmod +x hello.sh 。
增加执行权限后可以执行 ./hello.sh 来运行该脚本,也可以使用 bash -x hello.sh 以调试模式运行脚本。
系统命令
上面是通过 ./hello.sh 的方式执行的脚本文件,每次都需要添加路径,那么我们可以像执行 ls 一样,直接执行 hello.sh 吗,答案是可以的。
echo $PATH获取系统里所有可以被直接执行程序的路径。sudo cp hello.sh /usr/bin将hello.sh拷贝到上诉任意一个path路径路径中,这里拷贝到/usr/bin。- 现在可以直接运行
hello.sh命令而不需要添加路径了。
echo 命令
echo 命令的作用是在屏幕输出一行文本,可以将该命令的参数原样输出。
echo hello world # 输出当行文本
# 输出多行文本
echo "
hello
world
"
复制代码
解析转义字符
用 -e 参数使得 echo 可以解析转义字符
echo -e "hello \n world" # 如果不添加 -e 则会原样输出,添加了 -e 输出则会换行
复制代码
引号
Bash 中有3种引号类型:
- 单引号
'',单引号用于保留字符的字面含义,各种特殊字符在单引号里面,都会变为普通字符,比如星号*、美元符号$、反斜杠\等。 - 双引号
"",双引号比单引号宽松,大部分特殊字符在双引号里面,都会失去特殊含义,变成普通字符。三个特殊字符除外:美元符号$、反引号 ``` 和反斜杠\。 - 反引号 `` ,要求
Shell执行被它括起来的内容,例如执行 echo `pwd`,相当于直接执行pwd命令 。
变量
Bash 没有数据类型的概念,所有的变量值都是字符串。
环境变量
环境变量是 Bash 环境自带的变量,进入 Shell 时已经定义好了,可以直接使用。它们通常是系统定义好的,可以理解成全局的常量。
常见环境变量种类:
BASHPID:Bash进程的进程ID。EDITOR:默认的文本编辑器。HOME:用户的主目录。HOST:当前主机的名称。LANG:字符集以及语言编码,比如zh_CN.UTF-8。PATH:由冒号分开的目录列表,当输入可执行程序名后,会搜索这个目录列表。PWD:当前工作目录。SHELL:Shell的名字。TERM:终端类型名,即终端仿真器所用的协议。UID:当前用户的ID编号。USER:当前用户的用户名。
环境变量相关命令:
env # 显示所有环境变量。
echo $PATH # 单独输出PATH环境变量
# 自定义环境变量
1. vim .bashrc # 进入bash的配置文件
2. export EDITOR=vim # 写入一个全局变量EDITOR并赋值vim
复制代码
创建变量
- 字母、数字和下划线字符组成。
- 第一个字符必须是一个字母或一个下划线,不能是数字。
- 不允许出现空格和标点符号。
message="Hello World" # message 为变量名
复制代码
使用变量
使用上面创建的变量,需要在变量名前面添加美元符号 $
echo $message # 打印message变量
复制代码
参数变量
可以这样调用我们的脚本文件 ./variable.sh 参数1 参数2 ... 其中参数1、参数2...被称为“参数变量”。
在 Shell 脚本中可以通过以下变量获取参数:
$#参数的数目$0被运行的脚本名称$1第一个参数$2第二个参数$N第N个参数
使用 shift 命令来挪移变量值:
# shift.sh(具体内容)
#!/bin/bash
echo "第一个参数是 $1"
shift
echo "第一个参数是 $1"
# 控制台
./shift.sh p1 p2 p3
第一个参数是 p1
第一个参数是 p2
复制代码
同样是 $1 ,通过 shift 使得它的值会变成原本是 $2 的值。因此 shift 命令常被用在循环中,使得参数一个接一个地被处理。
数组
#!/bin/bash
# 定义数组
array=('v1' 'v2' 'v3')
# 访问数组
echo ${array[2]} # 访问数组(bash下标是从0开始)
echo ${array[*]} # 使用*号访问数组所有的值
复制代码
数学运算
在 Bash 中,所有的变量都是字符串, Bash 本身不会操作数字,因此它也不会做运算。不过可以使用 let 命令来实现运算。
#!/bin/bash
let "a = 5"
let "b = 2"
let "c = a + b"
echo "c = $c" # 输出 c = 7
复制代码
在 bash 中可用的运算符有以下几种:
| 运算 | 符号 |
|---|---|
| 加法 | + |
| 减法 | - |
| 乘法 | * |
| 除法 | / |
| 幂(乘方) | ** |
| 余(整数除法的余数) | % |
read
请求输入, read 命令读取到的文本会立即被存储在一个变量里。read.sh
#!/bin/bash
read name
echo "hello $name !"
复制代码
执行 ./read.sh 时,会发现光标处于接收输入的状态,此时我们输入一个字符串 lion 按下回车键后,控制台会打印出 hello lion 。
同时给几个变量赋值
可以用 read 命令一次性给多个变量赋值, read 命令一个单词一个单词(空格分开)地读取你输入的参数,并且把每个参数赋值给对应的变量。
#!/bin/bash
read oneName towName
echo "hello $oneName $towName !"
复制代码
显示提示信息
read 命令的 -p 参数, p 是 prompt 的首字母,表示“提示”。
#!/bin/bash
read -p "请输入您的姓名:" name
echo "hello $name !"
复制代码
限制字符数目
用 -n 参数可以限制用户输入的字符串的最大长度(字符数)
read -p "请输入您的姓名:" -n 5 name
复制代码
限制输入时间
用 -t 参数可以限定用户的输入时间(单位:秒)超过这个时间,就不读取输入了。
read -p "请输入您的姓名:" -n 5 -t 10 name
复制代码
隐藏输入内容
用 -s 参数可以隐藏输入内容,在用户输入密码时使用。
read -p "请输入密码:" -s password
复制代码
条件语句
任意语言都有条件语言,用它来做条件判断。
if 格式
if [ 条件测试 ] # 条件测试左右必须要有空格
then
...
fi # 结束符
或者
if [ 条件测试 ]; then
...
fi
复制代码
实例:
name="lion"
if [ $name = 'lion' ]; then # 这里使用 = 做判断条件,而不是 ==
echo "hello $name"
fi
复制代码
if else 格式:
if [ 条件测试 ]
then
...
else
...
fi
复制代码
实例:
if [ $n1 = $n2 ]
then
echo "n1=n2"
else
echo "n1!=n2"
fi
复制代码
if else elif 格式
if [ 条件测试1 ]
then
....
elif [ 条件测试2 ]
then
...
elif [ 条件测试3 ]
then
...
else
...default
fi
复制代码
实例:
#!/bin/bash
if [ $1 = "lion" ]
then
echo "hello lion"
elif [ $1 = "frank" ]
then
echo "hello frank"
else
echo "我不认识你"
fi
复制代码
条件测试
不同的测试类型:
- 测试字符串
- 测试数字
- 测试文件
测试字符串:
$string1 = $string2表示两个字符串是否相等。$string1 != $string2表示两个字符串是否不相等。-z $string表示字符串string是否为空。-n $string表示字符串string是否不为空。
测试数字:
$num1 -eq $num2equal的缩写,表示两个数字是否相等。$num1 -ne $num2not equal的缩写,表示两个数字是否不相等。$num1 -lt $num2lower than的缩写,表示num1是否小于num2。$num1 -le $num2lower or equal的缩写,表示num1是否小于或等于num2。$num1 -gt $num2greater than的缩写,表示num1是否大于num2。$num1 -ge $num2greate or equal的缩写,表示num1是否大于或等于num2。
测试文件:
-e $fileexist的缩写,表示文件是否存在。-d $filedirectory的缩写,表示文件是否为一个目录。-f $filefile的缩写,表示文件是否是一个文件。-L $fileLink的缩写,表示链接。-r $filereadable的缩写,表示文件是否可读。-w $filewritable的缩写,表示文件是否可写。-x $fileexecutable的缩写,表示文件是否可执行。$file1 -nt $file2表示文件file1是否比file2更新。$file1 -ot $file2表示文件file1是否比file2更旧。
同时测试多个条件:
&&表示逻辑与,只要有一个不为真,整个条件测试为假。||表示逻辑或,只要有一个为真,整个条件测试就为真。!表示反转测试条件。
#!/bin/bash
read -p "输入文件路径:" file
if [ ! -e $file ]
then
echo "$file 不存在"
else
echo "$file 存在"
fi
复制代码
case 测试多个条件
#!/bin/bash
case $1 in
"lion")
echo "hello lion"
;;
"frank" | "frank1" | "frank2") # 这里的逻辑或是一根竖线
echo "hello frank*"
;;
*)
echo "我不认识你"
;;
esac
复制代码
把它理解成普通编程语言中的 swtich ... case 即可。
循环语句
使我们可以重复一个代码块任意多次。Bash 中有3中类型的循环语句:
while循环until循环for循环
while 循环
while [ 条件测试 ]
do
...
done # 结束
复制代码
实例:
#!/bin/bash
while [ -z $response ] || [ $response != 'yes' ] # 输入的语句为空或者不是yes就会一直循环
do
read -p 'Say yes:' response
done
复制代码
until 循环
它的执行逻辑和 while 正好相反。
until [ 条件测试 ] # 条件测试为假会执行do,条件测试为真是结束循环
do
...
done # 结束
复制代码
实例:
#!/bin/bash
while [ $response = 'yes' ] # 当 response 输入为 yes 时会结束循环,否则一直循环
do
read -p 'Say yes:' response
done
复制代码
for 循环
主要用于遍历列表
for 变量 in '值1' '值2' '值3' '值4'
do
...
done
复制代码
实例:
#!/bin/bash
# 遍历一组值
for animal in 'dog' 'cat' 'pig'
do
echo "$animal"
done
# 遍历 ls 命令的执行结果
listfile=`ls`
for file in $listfile
do
echo "$file"
done
# 借助 seq 的 for 循环(seq后面会详细讲解)
for i in `seq 1 10`
do
echo $i
done
复制代码
函数
函数是实现一定功能的代码块,函数还是重用代码的一种方式。
函数名 (){
函数体
}
复制代码
- 函数名后面的圆括号中不加任何参数,这点与主流编程语言不相同。
- 函数的完整定义必须置于函数的调用之前。
实例:
#!/bin/bash
print_something(){
echo "我是一个函数"
}
print_something # 调用
复制代码
传递参数
#!/bin/bash
print_something(){
echo "hello $1" # $1 获取第一个参数
}
print_something Lion # Lion 为参数
print_something Frank # Frank 为参数
复制代码
函数返回值
Shell 的函数可以返回一个状态,也用的是 return 关键字
#!/bin/bash
print_something(){
echo "Hello $1"
return 1
}
print_something Lion
echo "函数的返回值是 $?" # $? 获取到的是上一个函数的返回值
复制代码
统计文件行数实例:
#!/bin/bash
line_in_file(){
cat $1 | wc -l
}
line_num=$(line_in_file $1) # 函数的返回值赋给变量了
echo "这个文件 $1 有 $line_num 行"
复制代码
函数的局部变量
#!/bin/bash
local_global(){
local var1='local 1' # 通过 local 关键字定义局部变量
echo "var1 is $var1"
}
local_global
复制代码
函数重载命令
可以用函数来实现命令的重载,也就是说把函数的名字取成与我们通常在命令行用的命令相同的名字,重载需要用到 command 关键字。
重载 ls 命令实例:
#!/bin/bash
ls (){
command ls -lh
}
ls
复制代码
实战练习
需求分析
统计下面这段文本26个英文字母(从 a 到 z )出现的次数。words.txt
# 展示文本部分内容
Abigail
Ada
Adela
Adelaide
Afra
Agatha
Agnes
Alberta
Alexia
Alice
Alma
Alva
...
复制代码
最终希望输出这样的结果:
A - 230
C - 57
D - 21
...
复制代码
思路分析
在之前学习的知识中,我们知道 grep 可以查找文件中的关键字。
grep命令可以帮助我们找到words.txt文本中所有出现的字母a,并且希望忽略大小写,grep-io a words.txt。- 通过管道符传递给
wc -l命令这样就可以统计到数据了,grep -io a words.txt | wc -l,这样就能统计到a字母的出现次数了。 - 结合
for循环,我们可以遍历字母a - z,去统计每个字母出现次数。 - 最后对统计结果使用
sort命令进行排序,就可以获取到我们想要的结果。
代码实现
#!/bin/bash
# 判断是否有参数
if [ -z $1 ]
then
echo "请输入文件"
exit # 没有参数则退出
fi
# 判断文件是否存在
if [ ! -e $1 ]
then
echo "文件为空"
exit # 文件为空则退出
fi
# 定义统计函数
statistics(){
for char in {a..z} # 循环字母a-z
do
# 这里的echo起的不是打印的作用,而是输出一个字符串,从而可以使用管道符进行转换,最后输出到tmp.txt文件中
echo "$char - `grep -io "$char" $1 | wc -l`" | tr /a-z/ /A-Z/ >> tmp.txt
done # 循环结束
sort -rn -k 2 -t - tmp.txt # 排序并打印到控制台
rm tmp.txt # 删除tem.txt
}
statistics $1 # 调用函数并传入 $1 参数
复制代码
tr /a-z/ /A-Z/是用来转换所有小写字母为大写。>>重定向输出追加到一个临时文件末尾。sort -rn -k 2 -t - tmp.txt对这个临时文件按数字进行排序并打印到控制台。
小结
通过本文,我们学习了编写 Shell 脚本的一些基本语法:变量、数组、条件语句、循环语句以及函数,最后我们通过一个统计字母出现的次数作为一个小练习,巩固了所学的知识。
但其实编写 Shell 脚本还需要掌握最重要的文本处理三剑客 sed 、 awk 、 grep ,并且还需要掌握一些更加高级的技能使得我们可以实实在在地编写出工作中有实际用途的脚本,而不是停留在写 Demo 阶段。作者将会在《Shell 脚本进阶篇》中来主攻这些知识。
[转帖]学习如何编写 Shell 脚本(基础篇)的更多相关文章
- Linux shell脚本基础学习详细介绍(完整版)一
Linux shell脚本基础学习这里我们先来第一讲,介绍shell的语法基础,开头.注释.变量和 环境变量,向大家做一个基础的介绍,虽然不涉及具体东西,但是打好基础是以后学习轻松地前提.1. Lin ...
- 详细介绍Linux shell脚本基础学习
Linux shell脚本基础学习这里我们先来第一讲,介绍shell的语法基础,开头.注释.变量和 环境变量,向大家做一个基础的介绍,虽然不涉及具体东西,但是打好基础是以后学习轻松地前提.1. Lin ...
- Linux shell脚本基础学习详细介绍(完整版)二
详细介绍Linux shell脚本基础学习(五) Linux shell脚本基础前面我们在介绍Linux shell脚本的控制流程时,还有一部分内容没讲就是有关here document的内容这里继续 ...
- Shell脚本基础学习
Shell脚本基础学习 当你在类Unix机器上编程时, 或者参与大型项目如k8s等, 某些框架和软件的安装都是使用shell脚本写的. 学会基本的shell脚本使用, 让你走上人生巅峰, 才怪. 学会 ...
- [转帖]编写shell脚本所需的语法和示例
编写shell脚本所需的语法和示例 https://blog.csdn.net/CSDN___LYY/article/details/100584638 在说什么是shell脚本之前,先说说什么是sh ...
- linux 的基本操作(编写shell 脚本)
终于到shell 脚本这章了,在以前笔者卖了好多关子说shell脚本怎么怎么重要,确实shell脚本在linux系统管理员的运维工作中非常非常重要.下面笔者就带你正式进入shell脚本的世界吧. 到现 ...
- 如何使用zx编写shell脚本
前言 在这篇文章中,我们将学习谷歌的zx库提供了什么,以及我们如何使用它来用Node.js编写shell脚本.然后,我们将学习如何通过构建一个命令行工具来使用zx的功能,帮助我们为新的Node.js项 ...
- Linux学习-->如何通过Shell脚本实现发送邮件通知功能?
1.安装和配置sendmail 不需要注册公网域名和MX记录(不需要架设公网邮件服务器),通过Linux系统自带的mail命令即可对公网邮箱发送邮件.不过mail命令是依赖sendmail的,所以我们 ...
- Git学习-->如何通过Shell脚本自动定时将Gitlab备份文件复制到远程服务器?
一.背景 在我之前的博客 git学习--> Gitlab如何进行备份恢复与迁移? (地址:http://blog.csdn.net/ouyang_peng/article/details/770 ...
- 什么是Shell?Shell脚本基础知识详细介绍
这篇文章主要介绍了什么是Shell?Shell脚本基础知识介绍,本文是一篇Shell脚本入门文章,在本文你可学到什么是Shell.有多少种Shell.一个Shell脚本代码实例,需要的朋友可以参考下 ...
随机推荐
- LeetCode 947. 移除最多的同行或同列石头 并查集
传送门 思路 干货太干就不太好理解了,以下会有点话痨( ̄▽ ̄)" 首先题目给了一个二维stones数组,存储每个石子的坐标,因为在同行或者同列的石子最终可以被取到只剩下一个,那么我们将同行同 ...
- Boost程序库完全开发指南:1.2-C++基础知识点梳理
主要整理了N多年前(2010年)学习C++的时候开始总结的知识点,好长时间不写C++代码了,现在LLM量化和推理需要重新学习C++编程,看来出来混迟早要还的. 1.const_cast <n ...
- 神经网络基础篇:关于 python_numpy 向量的说明(A note on python or numpy vectors)
关于 python_numpy 向量的说明 主要讲Python中的numpy一维数组的特性,以及与行向量或列向量的区别.并说一下在实际应用中的一些小技巧,去避免在coding中由于这些特性而导致的bu ...
- 学了这么久的高并发编程,连Java中的并发原子类都不知道?
摘要:保证线程安全是 Java 并发编程必须要解决的重要问题,本文和大家聊聊Java中的并发原子类,看它如何确保多线程的数据一致性. 本文分享自华为云社区<学了这么久的高并发编程,连Java中的 ...
- 六一儿童节,看我用ModelArts让8090梦回童年
[本期推荐] 8岁小朋友的儿童节,有点硬核,一起来认识这些小小程序员,看他们如何coding出一个与众不同的童年. 摘要: 如果还能再过一次儿童节-- 本文分享自华为云社区<"梦回童年 ...
- 火出边际的Serverless,你居然还不了解?
摘要:图灵奖获得者David A. Patterson和Spark共同创始人Ion Stoica,在19年伯克利的会议上发布Serverless将是下一代默认的计算范式. 本文分享自华为云社区< ...
- 企业诊断屋:服饰美妆电商如何用A/B测试赋能业务
更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 随着社会经济复苏,服饰美妆的消费市场回暖,国潮品牌正强势崛起和海外品牌进军,让不断增长的美妆市场竞争更加加剧. ...
- Python 将省、市 json 替换 成拼音
1.将 city_code_cn.json 中的省.市.区,翻译成英文,或直接替换去掉省.市 如:苏州市 -> 苏州 转成拼音后就变成 Suzhou,否则就会转成 Suzhoushi 怪怪的 ...
- NBA赛事直播超清画质背后:阿里云视频云「窄带高清2.0」技术深度解读
在半月前结束的NBA总决赛中,百视TV作为全网唯一采用"主播陪你看NBA"模式的直播平台,以"陪看型"赛事解说来面对内容差异化竞争.与此同时,百视TV还运用了& ...
- 【库函数】在什么时候使用 string_view 代替 string
前言 C++17增加了std::string_view,它在很多情况会优于使用std::string . 尤其是用做函数形参的时候,使用std::string_view基本一定优于老式的const s ...