(该系列文章大部分内容来源于MIT课程笔记,加入了个人的理解、原笔记中没有的细节和其他的需要理解的内容,公开课地址:https://www.bilibili.com/video/BV14E411J7n2?p=1)
上一讲:Overview+Shell笔记
下一讲:编辑器(Vim)
目录
Shell 脚本
大多数shell都有自己的一套脚本语言,包括变量、控制流和自己的语法。shell脚本与其他脚本语言不同之处在于,shell脚本针对shell所从事的相关工作进行来优化。因此,创建命令流程(pipelines)、将结果保存到文件、从标准输入中读取输入,这些都是shell脚本中的原生操作,这让它比通用的脚本语言更易用。本节中,我们会专注于bash脚本,因为它最流行,应用更为广泛。
在bash中为变量赋值的语法是foo=bar,访问变量中存储的数值,其语法为 $foo。 需要注意的是,foo = bar (使用空格隔开)是不能正确工作的,因为解释器会调用程序foo 并将 = 和 bar作为参数。 总的来说,在shell脚本中使用空格会起到分割参数的作用,有时候可能会造成混淆,请务必多加检查。
Bash中的字符串通过’ 和 "分隔符来定义,但是它们的含义并不相同。以’定义的字符串为原义字符串,其中的变量不会被转义,而 "定义的字符串会将变量值进行替换:
lilhoe@LilHoedeMacBook-Pro Downloads % hsj=ad
lilhoe@LilHoedeMacBook-Pro Downloads % echo $hsj
ad
lilhoe@LilHoedeMacBook-Pro Downloads % echo "$hsj"
ad
lilhoe@LilHoedeMacBook-Pro Downloads % echo '$hsj'
$hsj
lilhoe@LilHoedeMacBook-Pro Downloads %
bash 也支持函数,它可以接受参数并基于参数进行操作。下面这个函数是一个例子,它会创建一个函数并使用cd进入该文件夹。首先输入vim adh.sh
进入文件编程模式,点击i
进入修改模式:
mcd () {
mkdir -p "$1"
cd "$1"
}
这里 $1 是脚本到第一个参数。与其他脚本语言不同到是,bash使用了很多特殊到变量来表示参数、错误代码和相关变量。下面是列举来其中一些变量,更完整到列表可以参考 这里。
$0 - 脚本名
$1 到 $9 - 脚本到参数。
$1 是第一个参数,依此类推。
$@ - 所有参数
$# - 参数个数
$? -前一个命令到返回值
$$ - 当前脚本到进程识别码
!! - 完整到上一条命令,包括参数。常见应用:当你因为权限不足执行命令失败时,可以使用sudo !!再尝试一次。
$_ - 上一条命令的最后一个参数。如果你正在使用的是交互式shell,你可以通过按下 Esc 之后键入 .来获取这个值。
最后键入:q!
退出,若文件有修改键入:wq
保存退出。
通过source adh.sh
指令执行adh.sh
文件,看似没有事情发生,但是adh文件已经被执行。输入mcd test
会从工具目录转到测试目录。
命令通常使用 STDOUT来返回输出值,使用STDERR 来返回错误及错误码,便于脚本以更加友好到方式报告错误。 返回码或退出状态是脚本/命令之间交流执行状态到方式。返回值0表示正常执行,其他所有非0的返回值都表示有错误发生。
退出码可以搭配&& (与操作符) 和 || (或操作符)使用,用来进行条件判断,决定是否执行其他程序。同一行的多个命令可以用 ; 分隔。程序 true 的返回码永远是0,false 的返回码永远是1。
另一个常见的模式是以变量的形式获取一个命令的输出,这可以通过 命令替换 (command substitution)实现。
当您通过 $( CMD )
这样的方式来执行CMD 这个命令时,然后它的输出结果会替换掉 $( CMD )
。例如,如果执行 for file in $(ls)
,shell首先将调用ls ,然后遍历得到的这些返回值。
还有一个冷门的类似特性是 进程替换(process substitution), <( CMD ) 会执行 CMD 并将结果输出到一个临时文件中,并将 <( CMD ) 替换成临时文件名。这在我们希望返回值通过文件而不是STDIN传递时很有用。例如, diff <(ls foo) <(ls bar)
会显示文件夹 foo 和 bar 中文件的区别。
下面这个例子展示了一部分上面提到的特性。这段脚本会遍历我们提供的参数,使用grep 搜索字符串 foobar,如果没有找到,则将其作为注释追加到文件中。将如下文件保存到example.sh中:
#!/bin/bash
echo "Starting program at $(date)" # date会被替换成日期和时间
echo "Running program $0 with $# arguments with pid $$"
for file in $@; do
grep foobar $file > /dev/null 2> /dev/null
# 如果模式没有找到,则grep退出状态为 1
# 我们将标准输出流和标准错误流重定向到Null,因为我们并不关心这些信息
if [[ $? -ne 0 ]]; then
# -ne for "not equal", for more details see "man test"
echo "File $file does not have any foobar, adding one"
echo "# foobar" >> "$file"
fi
done
在bash中进行比较时,尽量使用双方括号 [[ ]] 而不是单方括号 [ ],这样会降低犯错的几率,尽管这样并不能兼容 sh。 更详细的说明参见这里。
当执行脚本时,我们经常需要提供形式类似的参数。bash使我们可以轻松的实现这一操作,它可以基于文件扩展名展开表达式。这一技术被称为shell的 通配( globbing)。
通配符 - 当你想要利用通配符进行匹配时,你可以分别使用 ? 和 * 来匹配一个或任意个字符。例如,对于文件foo, foo1, foo2, foo10 和 bar, rm foo?这条命令会删除foo1 和 foo2 ,而