Bash 老司机也可能忽视的 10 大编程细节

前端之家收集整理的这篇文章主要介绍了Bash 老司机也可能忽视的 10 大编程细节前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

Bash,作为大部分 Linux 发行版的出厂预设 Shell,因其晦涩难懂的语法设置,以及需要特别留心的编程细节,几乎成为 Linux 区别于其他操作系统的代名词。针对 Bash 中一些极容易出错的细节,我们在这里总结了 10 条编程注意事项,希望对各位泛 Linux 环境的开发者有所裨益。原文来自一位名叫 Julia Evans 的开发者博客,雷锋网编译。

作为一名 Bash 脚本编写经验超过 10 年的老程序员,我通常不用 Bash 处理复杂的编程任务。但作为一款我们在日常 Linux 使用中几乎无法避免的通用工具,Bash 的确有许多与我们习以为常的 C++ 和 Java 等高级语言非常不同的基础特性。在这里我并不打算讨论 Bash 编程的高阶应用,而是仅仅针对 Bash 中那些与众不同的基础特性做一简单梳理和汇总。希望对各位有所帮助。

当然,如果你对阅读博客不感兴趣,这里我再顺便推荐两个开源免费的小工具。一个是 Shell 语法检查工具 shellcheck,可以在运行前对脚本进行全面的语法检查;另一个是 shfmt,可以自动对写好的 Shell 脚本按照要求格式化。

shellcheck 地址:https://www.shellcheck.net/

shfmt 地址:https://github.com/mvdan/sh

@H_301_14@1. 等号两边慎用空格

Bash 中的赋值语句通常都是这样的:

VARIABLE=2

然后我们通过 $VARIABLE 引用该变量。这里有一点非常重要,也极容易忽视的就是:千万不要在等号两边加空格。虽然加上空格也不会引起语法错误,但很可能造成意想不到的结果。例如 VARIABLE= 2 这个语句,解释器很可能会将一个空字符串赋值给 VARIABLE,然后运行一个名字叫 2 的脚本。

一般常用的 Bash 变量都是字符串,我很少见到有数组的。另外,虽然解释器也接受小写,但 Bash 中默认是将变量名全部大写的。

2. 用 ${} 限定变量名

例如我定义了一个变量 MYVAR,内容是字符串“file.txt”,然后想执行如下命令:

mv $MYVAR $MYVAR__bak # wrong!

结果一定会报错。因为解释器会搜索 MYVAR__bak 这个变量,而我们根本没有定义。因此,为了避免出现类似问题,最好的办法是每次引用时都在变量两边加上括号,就像这样:

mv ${MYVAR} ${MYVAR}__bak # right!

3. 区分全局变量、局部变量和环境变量

Bash 有三种变量:全局变量、局部变量和环境变量。其中最常用的是环境变量。

实际上每个 Linux 进程都有许多预设的环境变量(运行 env 命令可查看),Bash 中对环境的变量的应用非常简单。例如,想要查看 MYVAR 环境变量的值,可以运行下面这条命令:

echo "$MYVAR"

想要设置环境变量,可以用这条命令:

export MYVAR=2

需要注意的是,一旦在进程中设置了环境变量,那么这个环境变量会在所有与其相关的子进程中生效,例如下面这个例子:

export MYVAR=2; python test.py

$MYVAR 环境变量也会在 test.py 脚本中生效。

另一种是全局变量,如下所示这样的赋值语句实际上就是在定义全局变量

MYVAR=2

全局变量就像其他编程语言一样,会在整个代码中生效。

最后一种是局部变量,这种变量通常只在一个循环语句或者 Bash 函数中有效。一般不常用。

4. 活用命令替换

通常我会用下面这段 for 循环打印输出 1-10 这 10 个数字。

for i in `seq 1 10` # you can use {1..10} instead of `seq 1 10`

do

echo "$i"

done

如果把这些代码写到一行里,是这样的:

for i in `seq 1 10`; do echo $i; done

这里我想强调的是,通过反引号(即键盘上Tab键上方的按键,注意不是单引号)将 seq 命令的输出结果,嵌入了 for 循环中直接使用。通过类似这种命令替换的方式,我们可以大大减少代码冗余,同时减少代码的出错几率。常见的替换方式有如下两种:

OUTPUT=`command`

# or

OUTPUT=$(command)

5. if 的注意事项

if 语句的判定条件同时支持单中括号([])和双中括号([[]]),他们都可以用来隔离表达式和 if 关键词。但这里推荐使用双中括号,因为它的容错率更高,而且支持更多功能。另外,在 Linux 中单中括号 [ 实际与 test 命令是等价的,因此用双括号显然能避免更多的麻烦。

例如下面这段代码

If [[ -e /tmp/awesome.txt ]]; then

echo "awesome"

fi

可以判断 awesome.txt 文件是否存在。

再比如下面的场景:

$ [ 3 < 4 ] && echo "true"

bash: 4: No such file or directory

$ [[ 3 < 4 ]] && echo "true"

true

使用单中括号会报错,但双中括号就没问题。

除了使用双中括号之外,还可以用 test 命令的运行结果作为 if 语句的判断条件,例如:

test -e /tmp/awesome.txt

如果 awesome.txt 文件存在,则命令返回 0,否则返回错误码。

实际上,除了常见的 test 命令,所有返回固定数值的命令都可以作为 if 语句的判断条件。例如下面的代码

if grep peanuts food-list.txt

then

echo "allergy allert!"

利用 grep 搜索关键词,然后根据结果打印警告信息。

6. 使用函数

在 Bash 中定义和使用函数非常简单(特别是无参函数)。例如:

my_function () {

echo "This is a function";

}

my_function # calls the function

代码中定义了一个 my_function 函数调用时也只需要写函数名。

7. 用双引号引用变量

前面第 2 条提到要用 ${} 限定变量名的范围,这里要说的是利用引号限定变量值的范围。

例如下面代码

X="i am awesome"

Y="i are awesome"

if [ $X = $Y ]; then

echo awesome

实际上会报错,因为解释器会将 if 语句的判定条件理解为:

if [ i am awesome == i are awesome ]

为了避免这种错误,就必须用双引号限定变量值的范围。

if [ "$X" = "$Y" ]; then # i put quotes because i know bash will betray me otherwise

这样写就没问题了。当然,如果变量值不包括空格,那不带引号也能得到同样的结果,但毕竟带上双引号会让程序更可靠。

8. 关于返回值

每一个 Linux 程序都有返回值,按照规范,这个返回值在 0-127 之间,0 表示成功,其他值是含义各不相同的错误码。在 Bash 中充分利用这一点可以增加程序的灵活性。例如:

create_user && make_home_directory

这条语句,只有 create_user 返回 0 时,才会执行 make_home_directory。

create_user; make_home_directory

则表示无论 create_user 的返回值是什么,都会执行 make_home_directory。

类似的,你也可以通过:

create_user || make_home_directory

表示只有当 create_user 返回非 0 值时,才会执行 make_home_directory。

9. 使用后台任务

在 Bash 中,可以通过在命令后添加 & 符号实现后台多任务。例如:

long_running_command &

把进程放入后台后,还可以通过 fg 命令将其切换到前台。如果后台命令过多,可以先通过 jobs 命令查看进程的 job ID,然后用 fg+job ID 的方式将指定的后台进程切换到前台

另外,还可以通过 wait 命令控制多任务的执行顺序。例如:

long_running_command1

wait

long_running_command2

表示在命令 1 执行结束后才执行命令 2。

10. 活用 set 命令

在其他语言中,通常遇到错误的语句时,编译器就会报错并停止运行,但 Bash 不会。例如下面的代码

python non_existant_file.py

echo "done"

无论 non_existant_file.py 脚本是否存在,Bash 都会打印输出 done。因此为了保证代码的安全性和正确性,我们可以在代码中用

set -e

对 Bash 环境进行一些额外设置,-e 表示出现错误就停止。

类似的,在其他语言中,使用没初始化的变量也会报错,但 Bash 不会。例如下面的代码

rm -rf "$DIRECTORY/*"

如果 $DIRECTORY 没有提前初始化,Bash 也并不会停下来,而是直接以空字符串对待,那么这句命令的含义就变成了:尝试删除根目录下的所有文件,结果将非常严重。

这时就可以用

set -u

表示 Bash 不执行未定义的变量。

除了 -e 和 -u 之外,还有

set -x

表示每条命令执行之前必须先打印命令内容。此外还可以通过 set -o 显示所有可以设置的选项。

这也是为什么许多 shell 脚本都以 set -eu 或者 set -eux 等做为开头的原因,因为这样就可以让脚本运行在更安全的环境下。


转自https://www.leiphone.com/news/201703/i49ztcRDDymM7Id5.html

猜你在找的Bash相关文章