命令串联
管道
管道是 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 + A
和 Command + 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
这里虽然介绍的是函数返回值,但对整个脚本同样适用