Bash

课程概览与 shell · the missing semester of your cs education

Bourne Again SHell, 简称 “bash”

ls -l
ls <directory>
cd ~ # bring you to the home directory
cd -
man command # list the command manual to help you use particular command
rm
rm -r
rmdir
Control L # clear your terminal and go back to the top

cat
echo "hello" > hello.txt
cat < hello.txt > hello2.txt

ls -l / | tail -n1 #输出ls -l 的最后一行到屏幕上
ls -l / | tail -n1 > ls.txt

curl
grep
cut

tee # the command takes its input and writes it to a file but also to standard out
command | tee [选项] [文件...]
xdg-open
foo=bar
echo "$foo"
# 打印 bar
echo '$foo'
# 打印 $foo

和其他大多数的编程语言一样,bash 也支持 if, case, whilefor 这些控制流关键字。同样地, bash 也支持函数,它可以接受参数并基于参数进行操作。下面这个函数是一个例子,它会创建一个文件夹并使用 cd 进入该文件夹。

mcd () {
mkdir -p "$1"
cd "$1"
}

这里 $1 是脚本的第一个参数。与其他脚本语言不同的是,bash 使用了很多特殊的变量来表示参数、错误代码和相关变量。下面列举了其中一些变量,更完整的列表可以参考 这里

  • $0 - 脚本名
  • $1$9 - 脚本的参数。 $1 是第一个参数,依此类推。
  • $@ - 所有参数
  • $# - 参数个数
  • $? - 前一个命令的返回值
  • $$ - 当前脚本的进程识别码
  • !! - 完整的上一条命令,包括参数。常见应用:当你因为权限不足执行命令失败时,可以使用 sudo !! 再尝试一次。
  • $_ - 上一条命令的最后一个参数。如果你正在使用的是交互式 shell,你可以通过按下 Esc 之后键入 . 来获取这个值。

命令通常使用 STDOUT 来返回输出值,使用 STDERR 来返回错误及错误码,便于脚本以更加友好的方式报告错误。 返回码或退出状态是脚本/命令之间交流执行状态的方式。返回值 0 表示正常执行,其他所有非 0 的返回值都表示有错误发生。

退出码可以搭配 &&(与操作符)和 ||(或操作符)使用,用来进行条件判断,决定是否执行其他程序。它们都属于短路 运算符(short-circuiting) 同一行的多个命令可以用 ; 分隔。程序 true 的返回码永远是 0false 的返回码永远是 1。让我们看几个例子

false || echo "Oops, fail"
# Oops, fail

true || echo "Will not be printed"
#

true && echo "Things went well"
# Things went well

false && echo "Will not be printed"
#

false ; echo "This will always run"
# This will always run

另一个常见的模式是以变量的形式获取一个命令的输出,这可以通过 命令替换command substitution)实现。

当您通过 $( CMD ) 这样的方式来执行 CMD 这个命令时,它的输出结果会替换掉 $( CMD ) 。例如,如果执行 for file in $(ls) ,shell 首先将调用 ls ,然后遍历得到的这些返回值。还有一个冷门的类似特性是 进程替换process substitution), <( CMD ) 会执行 CMD 并将结果输出到一个临时文件中,并将 <( CMD ) 替换成临时文件名。这在我们希望返回值通过文件而不是 STDIN 传递时很有用。例如, diff <(ls foo) <(ls bar) 会显示文件夹 foobar 中文件的区别。

说了很多,现在该看例子了,下面这个例子展示了一部分上面提到的特性。这段脚本会遍历我们提供的参数,使用 grep 搜索字符串 foobar,如果没有找到,则将其作为注释追加到文件中。

#!/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
echo "File $file does not have any foobar, adding one"
echo "# foobar" >> "$file"
fi
done

在条件语句中,我们比较 $? 是否等于 0。 Bash 实现了许多类似的比较操作,您可以查看 test 手册。 在 bash 中进行比较时,尽量使用双方括号 [[ ]] 而不是单方括号 [ ],这样会降低犯错的几率,尽管这样并不能兼容 sh。 更详细的说明参见 这里