命令串联

管道

管道是 shell 中最常用的概念之一,它允许不同脚本、命令之间互相传递数据,举一个最常见的例子:

ls | grep 'a'

默认情况下,命令 ls 会把当前目录下的文件输出到屏幕上,但如果通过管道符号 |,它就会把输出结果传递给下一个命令。

命令 grep 恰好支持从管道中读取数据,因此上面这行脚本的含义实际上是在当前目录内寻找名称含有字母 a 的文件。

我们可以自己模拟一下:

function before {
    echo 'output'
}

function after {
    read in
    echo "Read from pipiline: "${in}
}

before | after
# 输出结果为:
# Read from pipiline: output

重定向

说到管道,就不得提提它的孪生兄弟:重定向,最简单的使用场景就是把原本输出到屏幕的内容,重定向到文件中。

当然,这只是重定向最简单的用途,如果不了解背后的运行原理,就会影响到后续的使用。

首先,*nix 系统中有三种特殊的文件描述符,其中 0 表示标准输入,它一般指的是我们的键盘,1 表示标准输出,2 表示错误输出,它们一般都表示屏幕。所以 Shell 可以理解为一个盒子,它从 0(标准输入,也就是键盘)读取命令,没有错误的话就输出到 1(标准输出),命令执行错误的话输出到 2(错误输出),最终都会在屏幕上显示出来。

举一个例子,请看下面这行代码:

ls exist.sh not_exist.sh 1>success 2>fail

这行代码的意思首先是要展示两个文件,假设一个文件存在,另一个文件不存在(从名字就能看出来了),这样会产生一行标准输出和一行错误输出。1>success 的意思是把标准输出重定向到 success 这个文件,类似的,2 > fail 表示把错误信息输出到 fail 这个文件。

类似的语法还可以写成:

ls exist.sh not_exist.sh >success 2>&1

这是因为如果 > 前面不加数字,默认是标准输出。而 2>&1 则表示让错误输出使用和标准输出相同的重定向方式。因此这个命令等价于 ls exist.sh not_exist.sh 1>success 2>success

从严格意义上讲,使用 2>&1 的效率更高一些,因为它会复用标准输出的管道。

过滤输出

有了上述背景的积累,我们来看一个实际的问题。有时候在 Shell 脚本中我们只希望用到一个命令的功能, 但不希望它产生任何输出,此时可以使用如下命令:

command > /dev/null 2>&1

这行命令表示把标准输出和错误输出都重定向到 /dev/null 文件,只是一个特定的文件,可以理解为**黑洞**。因为任何内容都可以写入这个文件,但对这个文件的读取永远会返回 EOF,也就是输入的任何内容都会被抛弃掉。

上述命令还可以简写为 command &>/dev/null,没有什么理由和解释,只不过是 > /dev/null 2>&1 缩略写法。

除了使用 > /dev/null 这种写法,还可以使用 >&-,它不表示重定向,而是表示直接关闭某种输出。自然屏幕上也就没有任何内容了。

更多类似的技巧请参考这篇文章:Difference between 2>>-, 2>/dev/null, |&, &>/dev/null and >/dev/null 2>&1

输入重定向

如果要想拷贝某个文件中的内容到剪贴板,笨的人打开文件按下 Command + ACommand + C,聪明一些的人会输入下面这个命令:

cat file | pbcopy

这种写法其实还可以再提高一下效率,因为它会读取文件,然后把原本输出到标准输出(屏幕)的内容通过管道转到 pbcopy 这个命令上。

更高效、更直接的写法如下:

pbcopy < file

这样可以减少一次 IO 操作

函数返回值

在函数的结尾可以使用 return 关键字,然而需要注意的是,调用函数后的返回结果,并不是 return 的内容,而是 echo 的内容。至于 return 的内容,则可以通过 $? 这个特殊变量来读取。

function foo {
    echo 'output'
    return 1
}

a=`foo`
echo $?        # 输出 1
echo $a        # 输出 output

if 语句中,除了可以进行普通的判断外,还可以直接根据命令的执行结果进行判断。此时读取的依然是 return 的结果。

前文中提过,正常执行的命令返回值是 0,对应到 if 语句中则是 true 分支:

function foo {
    return 1
}

if test ; then
    echo "1"
else
    echo "0"
fi

# 因为函数返回 1,表示执行失败,所以最终输出 0

前面曾经介绍过如何判断当前目录下是否存在某个文件,放到 if 中就可以写为:

if `ls | grep -q 'a'` ; then
    echo "yes"
else
    echo "no"
fi

这里虽然介绍的是函数返回值,但对整个脚本同样适用

results matching ""

    No results matching ""