在使用Linux系统的过程中, 经常会用到shell, 本文介绍shell脚本编程. 通过shell脚本, 能够将一系列固定的指令快速的执行, 在合适的场景下能够大幅度提高开发效率. 本文按照创建一个Shell脚本的顺序, 依次介绍各个环节涉及的知识.
在使用shell脚本时也需要注意, 由于语法设计堪称非常糟糕, 因此shell脚本不适合做复杂逻辑的处理. shell脚本适合简单调用shell指令的场景, 对于较为复杂的操作, 使用Python脚本进行处理可能更有优势. 例如使用shell操作Git通常都比较简单直观, 而对于处理一个YAML文件的正则替换问题, 使用Python更加的简明易懂.
帮助系统
如果当前存在一个Shell环境, 那么可以通过安装第三方软件tldr(Too long, don’t read)快速查询指令的常用方法, 例如
1 | root@iZ:~# tldr ps |
再也不用记指令参数了, 也比当场Google不知道快到那里去了
指定脚本解释器
在Shell脚本的第一行需要指定执行此脚本的解释器, 通常可以指定为
1 | ! /bin/bash |
bash是Bourne Again Shell, 是很多Linux系统的默认脚本解释器. 常见的解析器包括bash, sh, fish, zsh等. 不同的解释器语法规则存在差异, 因此虽然bash多数情况下是默认的选择, 但为了避免不必要的麻烦, 还是应该在每个脚本开头的位置都指明需要使用的解释器类型.
对于很多极简的docker镜像, 其中仅包含
sh, 此时应该将解释器类型指定为#! /bin/sh以便于最大程度的使得脚本可正常执行.
创建变量
变量规则
在Shell中使用name=value的格式来创建变量,而且在等号的两端不能包含空格, 如果值的部分包含空格, 则需要用引号包裹整个值. 例如
1 | $ EXNAME='Hello World' #等号两端一定不能有空格,否则是执行指令的含义 |
由于Shell使用空格分隔参数, 因此如果等号两端有空格, 就会将变量名当做指令名, 将等号和值当做第二和第三个参数
- 创建变量时不需要
$符号, 引用变量时加入$符号 - 大括号用于区分变量名的边界, 在不产生歧义的时候可以不加, 但建议始终加入大括号
特殊变量
| 变量名 | 含义 |
|---|---|
$0 |
当前脚本的文件名 |
$n |
传递给脚本或函数的参数 |
$# |
传递给脚本或函数的参数个数 |
$* |
传递给脚本或函数的所有参数 |
$@ |
传递给脚本或函数的所有参数 |
$? |
上个命令的退出状态, 或函数的返回值 |
$$ |
当前 Shell 进程 ID |
字符串与变量规则
- Shell中的字符串可以使用单引号, 双引号或者不使用引号
- 单引号内的内容原样输出, 而双引号中可以使用变量和转义字符
1 | $ foo=bar |
引号中无法使用
~表示当前路径, 应该使用$HOME变量代替
结果作为变量
Shell中可以将一个指令的返回结果作为另一个指令的参数, 只需要将待执行的指令使用$()包裹即可, 例如
1 | appId=$(lsof -i:4567 | awk '$1 == "python3" {print $2}') |
将一个指令使用反引号包裹, 也具有同样的效果, 不过我觉得这样不够明显, 容易看错
ANSI-C Quoting
大多数Shell都支持一种特殊的语法格式, 实现以ANSI-C格式直接引用一个转义字符, 例如
1 | echo $'\n' # 换行符 (LF) |
会在读取时转换为ASCII的0x0A, 即换行字符串. C语言中的大部分转义字符的用法均支持, 也支持8进制和16进制输入
在大多数情况下, 如果直接输入
\n只会按照字面量处理为斜杠和字符n, 无法实现换行的效果
Shell运算符
由于Shell的语法限制, Shell的运算符非常的反常规, 因此就不逐一记录了, 以后用到了在来查下面的链接
流程控制语句
if-else
1 | if [ expression 1 ] |
注意
- 由于Shell的设计,
[是一个指令, 因此[两端都需要有空格, 否则会解析错误 - 表达式部分直接写变量等价于判断此变量是否为空
函数
函数的格式如下所示
1 | function <funname> () { |
其中关键字function以及函数名后的()均可以省略. 可以向函数传递参数, 在函数内部使用$1等变量访问参数, 调用函数的方式与调用命令的方式相同, 例如
1 | funWithParam(){ |
Shell中的函数与其他编程语言中的函数概念不同, Shell中的函数更加类似于一段代码块. 因此并不能在Shell的函数中返回结果. 对于需要返回结果的场景, 通常直接使用全局变量.
重定向与管道
重定向和管道是我认为最具有Unix哲学的东西, 它们实际上是提供了一种组合的能力, 从而能将各类工具根据需要进行组合. 常见的重定向与管道命令以及一些相关的指令如下表所示:
| 符号/操作 | 名称 | 功能简介 |
|---|---|---|
command > file |
输出重定向 | 将 stdout 覆盖写入文件 |
command >> file |
追加输出重定向 | 将 stdout 追加到文件 |
command < file |
输入重定向 | 从文件获取 stdin |
command1|command2 |
管道 | 将 command1 的 stdout 作为 command2 的 stdin |
command 2> file |
错误重定向 | 将 stderr 覆盖写入文件 |
command &> file |
合并重定向 | 将 stdout 和 stderr 都重定向到文件 |
command |&command2 |
错误管道 | 将 stdout 和 stderr 都通过管道传递 |
command >> file 2>&1 |
经典合并追加 | 将 stdout 和 stderr 都追加到文件 |
<< EOF |
Here Document | 多行文本作为输入 |
/dev/null |
空设备 | 丢弃所有写入的数据 |
$(command) |
命令替换 | 将命令的输出结果作为参数 |
<(command) |
进程替换 | 将命令输出作为临时文件使用 |
&& |
逻辑与 | 前一个命令成功才执行下一个 |
|| |
逻辑或 | 前一个命令失败才执行下一个 |
重定向
总所周知, 在unix类的系统中, 每个程序会默认开启三个文件:
| 文件描述符 | 含义 | 默认实现 |
|---|---|---|
| 0 | 标准输入(stdin) | 从键盘读取 |
| 1 | 标准输出(stdout) | 输出到屏幕 |
| 2 | 标准错误输出(stderr) | 输出到屏幕 |
重定向符号实际上就是改变这些文件的指向. 具体来说, 操作系统提供了dup2系统调用, 可以修改文件描述符的指向. 在shell启动命令的进程前, 可通过系统调用将这些标准输入输出重新指向特定的文件, 从而实现重定向.
管道操作符
管道操作符|非常的有意思, 它的实现并不是先执行command1然后将输出作为command2的输入执行command2, 而是启动两个进程同时执行command1和command2, 并且将两者的输出和输入映射到同一个文件. 能体现这个实现的一个典型场景是使用grep指令查找进程, 例如
1 | > ps aux | grep xx |
在输出的进程列表中可以看到grep命令本身, 这正好说明了ps指令在输出进程列表时, grep指令的进程已经存在了.
操作系统也提供了pipe系统调用, 此调用返回两个文件描述符, 分别表示管道的输入端和输出端. shell将两个子进程的标准输出和标准输入映射到两个文件描述符即可实现管道的效果.
这也体现了为什么shell是
外壳了, 功能都是内核kernel实现的, shell提供了一层封装.
多行输入
<< EOF可实现多行输入, 例如
1 | cat << EOF |
命令替换与进程替换
$(command)执行 command 并将其标准输出的结果替换到当前命令行中
1 | # 将 date 命令的输出作为参数传递给 echo |
<(command)使命令的输出(或输入)表现得像一个临时文件
1 | # 比较两个命令输出的差异 |
Tee 分叉
tee 命令通常与管道连用, 将数据流传给下一个命令,又同时保存到一个文件中(像水管的一个 T 型三通接头)
1 | # 将 ls 的输出既显示在屏幕上,又保存到 filelist.txt 中 |
设置可执行权限
在运行脚本前需要对其赋予可执行权限, 例如对于脚本shell.sh, 可以执行如下指令授予其可执行权限.
1 | $ chmod +x shell.sh |
对于图像界面可以通过右键设置来赋予可执行权限
添加到搜索目录
此步骤不是必须的, 但是如果希望在任意位置都可以执行此脚本, 则可以将脚本放置在一个PATH变量包含的路径之中, 可以使用echo $PATH查看系统全部的搜索路径, 例如
1 | lizec@ideapad:~$ echo $PATH |
将脚本添加到上面的任意一个目录中即可
bash新增PATH路径
如果想要把某个目录添加到PATH变量之中, 可以直接修改$PATH变量的值, 例如
1 | echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc |
zsh新增PATH路径
以新增Go语言的bin目录到PATH路径为例, 打开~/.zshrc文件, 新增如下内容
1 | # Go PATH Configuration |
执行source指令使操作立即生效或者重新打开终端
fish新增PATH路径
如果使用的是fish, 则可以打开~/.config/fish/config.fish添加如下的内容
1 | set -gx PATH $PATH /usr/local/go/bin |
然后执行如下指令重新加载配置
1 | source ~/.config/fish/config.fish |
更多配置可以查看下面的链接
Bash常用快捷功能
| 指令 | 解释 | 说明 |
|---|---|---|
cd - |
回到上一次停留的目录 | |
!<num> |
快速执行history里的某个指定命令 | !743 |
!! |
指代上一个命令 | sudo !! 以管理员权限重新执行上一条指令 |
Shell常用判断语句
判断变量是否为空
1 | para1= |
判断文件和目录是否存在
1 | #如果文件夹不存在, 创建文件夹 |
shell写逻辑是真的太烂了, 这种东西让AI写就行了.
Shell其他常见功能
以下是一些常见的功能的Shell实例.
向文件写入多行数据
1 | # 创建 hook 钩子函数 |
cat>表示覆盖写入文件, ~/repos/"${ProjectName}.git"/hooks/post-receive是文件名, 文件名中可以使用变量, <<EOF表示结束符为EOF.
中间需要写入的的内容也可以使用变量.
参考资料
最后更新: 2026年05月01日 14:36
版权声明:本文为原创文章,转载请注明出处
原始链接: https://lizec.top/2019/06/09/Shell%E7%BC%96%E7%A8%8B%E7%AC%94%E8%AE%B0/