fangpsh's blog

关于Bash 你需要知道的十件事

读到一篇文章《Ten Things I Wish I’d Known About bash》,所以有了这个畅销书式的标题,一些笔记。
感觉作者写这本书主要是为了推销他的新书《Learn Bash the Hard Way》,不过其中十个关于Bash 的点,倒是挺有趣。

1) ` ` vs $()

这2个符号的效果一样,它们包含的内容都会被执行,然后再把结果赋值给变量,或者传递给其他命令,很常用:

$ echo `ls`
$ echo $(ls)

我个人常用` `,$() 不常用,它们2个有啥区别呢?看个例子:

$ echo `echo \`echo \\\`echo inside\\\`\``

$ echo $(echo $(echo $(echo inside)))

2条语句效果一样,不过后者可读性明显好太多,以后写这种嵌套命令的时候,用$()吧,不然debug 起来得瞎眼。

参考:

这里有个小插曲,我不知道怎么用Markdown 写出反引号(backticks)内一对反引号的效果: ` ` ,发了一条tweet,得到了答案,这个符号好像也可以叫:grave accent,沉音符:

`` ` ` `` 

2) 通配符 vs 正则表达式

这2个是完全不一样的东西,不懂作者说容易搞混。。。作者给的例子:

#输出目录下所有的文件
$ ls *

#输出目录下所有以. 开头的文件,是通配符,不是正则
$ ls .*

3) 返回码(Exit Codes)

Bash 的世界,0 是正常,非0 是异常。echo $? 可以得到上一条命令的返回码。其实是不是应该按照翻译叫退出码,不过编译语言里return 都叫返回码嘛。

常常用grep 的返回码来判断特定内容是否存在,是因为如果存在grep 会返回0,不存在返回1,放在if 语句里面,非常直观、方便!见下文。

4) if 语句,[ 和 [[

if grep not_there /dev/null
then
    echo hi
else
    echo lo
fi

以上脚本执行,输出lo ,非常直观有木有。

[[[ 的差别,前者好像是内置命令,后者是关键词,一般尽量用后者就是了,比较方便。详细区别对比:BASH 中单括号和双括号

# 直接执行会出错,$(grep note_there /dev/null) 输出为空,就变成了[ = '' ] ,
# 这也就是为什么常常在一些老脚本里面看到这样的语句: [ x$(...) = '']
if [ $(grep not_there /dev/null) = '' ]
then
    echo -n hi
else
    echo -n lo
fi


# [[ 就没有以上困扰
if [[ $(grep not_there /dev/null) = '' ]]
then
    echo -n hi
else
    echo -n lo
fi

5) set

脚本开头我一般都会加上:

set -eu

-u 表示遇到没定义的变量的时候,直接退出,可以防止如下的悲剧:

rm -rf ${ROOT}/

如果${ROOT} 没赋值,会发生什么?

-e 表示执行过程中遇到非0 的退出状态码,直接退出,结束执行,也可以防止某些异常情况。

完整的选择列表和解释:Advanced Bash-Scripting Guide: Chapter 33. Options

6) <()

作者说的这个我倒不常用,看起来挺方便。

$ grep somestring file1 > /tmp/a
$ grep somestring file2 > /tmp/b
$ diff /tmp/a /tmp/b

可以简化为:

diff <(grep somestring file1) <(grep somestring file2)

7) Quoting

A='123'
echo "$A"
echo '$A'

单引号里面的内容不会展开,输出结果为:

123
$A

作者文章里面这个例子:

mkdir -p tmp
cd tmp
touch a
echo "*"
echo '*'

不知道是因为版本原因还是配置问题,在我的机器上测试,都是输出 *,作者说的意想不到的结果大概是:

echo *

会输出当前目录下所有的文件名。

8) 最常用的三个快捷操作

  • !!,重复执行上一条命令
  • ~,展开为当前用户的home 目录

作者列了他最常用的3个快捷操作:

  • !@,展开为上一条命令的所有参数;
  • !:1-$:这条命令看着有点复杂,!表示上一条命令,:是分隔符,后面就是表示取到第几位。
    • !:2-3,展开为上一条命令的参数中的第2到第3位的内容。
bash-3.2$ echo 1 2 3 4 5
1 2 3 4 5
bash-3.2$ echo !:2-3
2 3
  • :h
grep isthere /long/path/to/some/file/or/other.txt
cd !$:h

!$:h 展开为 上一条命令最后一个参数的目录路径:"/long/path/to/some/file/or"。

9) 启动顺序

shell-startup-actual

作者放了1个图,左边是Bash,右边是Zsh,来源是:Shell startup scripts

非常有趣、直观,顺着一种颜色看,Bash 启动过程,期间加载的文件,一目了然。读这篇文章,最大的收获就是见到这张图。

10) getopts (cheapci)

作者说的chepci 这个项目没咋看,不过这段脚本里的片段 可以作为学习getopts 的例子