Skip to content

Unix Shell

约 2826 个字 38 行代码 预计阅读时间 10 分钟

本节是为生信分析学习为进行的Unix shell命令知识补齐,很多知识在HPC lab0中已经接触到了,这里进行一个复习和拓展。

Stream and Redirection

Redirecting Standard Output

我们都知道,Unix shell借助流来处理合并文件或输出它们的任务,其中最具代表性的命令绝对是cat

$ cat tb1-protein.fasta tba1-protein.fasta

输出如下:

teosinte-branched-1 protein
LGVPSVKHMFPFCDSSSPMDLPLYQQLQLSPSSPKTDQSSSFYCYPCSPP
FAAADASFPLSYQIGSAAAADATPPQAVINSPDLPVQALMDHAPAPATEL
GACASGAEGSGASLDRAAAAARKDRHSKICTAGGMRDRRMRLSLDVARKF
FALQDMLGFDKASKTVQWLLNTSKSAIQEIMADDASSECVEDGSSSLSVD
GKHNPAEQLGGGGDQKPKGNCRGEGKKPAKASKAAATPKPPRKSANNAHQ
VPDKETRAKARERARERTKEKHRMRWVKLASAIDVEAAAASVPSDRPSSN
NLSHHSSLSMNMPCAAA
teosinte-glume-architecture-1 protein
DSDCALSLLSAPANSSGIDVSRMVRPTEHVPMAQQPVVPGLQFGSASWFP
RPQASTGGSFVPSCPAAVEGEQQLNAVLGPNDSEVSMNYGGMFHVGGGSG
GGEGSSDGGT

在上述命令中,cat将两个文件链接到了一起,并通过标准输出流Standard Out输出。

而当我们未指定任何流输出的位置时,其默认被打印到我们的终端里,就像上面演示的那样,在Unix shell里,提供了重定向操作符>>>,它们可以将流输出的位置指向文件。

  • >: 将标准输出流重定向至一个文件,并且覆盖它。如果文件不存在,则会新建一个。
  • >>: 将标准输出流重定向至一个文件,追加内容。如果文件不存在,则会新建一个。
$ cat tb1-protein.fasta tba1-protein.fasta > zea-proteins.fasta
$ cat zea-proteins.fasta

输出如下:

teosinte-branched-1 protein
LGVPSVKHMFPFCDSSSPMDLPLYQQLQLSPSSPKTDQSSSFYCYPCSPP
FAAADASFPLSYQIGSAAAADATPPQAVINSPDLPVQALMDHAPAPATEL
GACASGAEGSGASLDRAAAAARKDRHSKICTAGGMRDRRMRLSLDVARKF
FALQDMLGFDKASKTVQWLLNTSKSAIQEIMADDASSECVEDGSSSLSVD
GKHNPAEQLGGGGDQKPKGNCRGEGKKPAKASKAAATPKPPRKSANNAHQ
VPDKETRAKARERARERTKEKHRMRWVKLASAIDVEAAAASVPSDRPSSN
NLSHHSSLSMNMPCAAA
teosinte-glume-architecture-1 protein
DSDCALSLLSAPANSSGIDVSRMVRPTEHVPMAQQPVVPGLQFGSASWFP
RPQASTGGSFVPSCPAAVEGEQQLNAVLGPNDSEVSMNYGGMFHVGGGSG
GGEGSSDGGT

Redirecting Standard Error

Unix shell提供了标准错误流Standar Error来输出错误或警告,在一般情况下,其和标准输出流中的内容一同被打印到终端中。Unix shell提供了2>2>>两种标准错误流的重定向符,其功能与>>>是类似的。

在以下例子中,我试图通过ls命令输出一个已经存在的文件zea-proteins.fasta和一个不存在的文件other.fasta

$ ls -l zea-proteins.fasta other.fatsa > normal.log 2> error.log
$ cat normal.log error.log

输出为:

-rw-r--r-- 1 yangshu233 yangshu233 503  5月 26 21:15 zea-proteins.fasta
$ ls: cannot access 'other.fatsa': No such file or directory

Note

在Unix系统上,所有被打开的文件(包括流)都会被分配一个唯一整数——文件描述符,其中标准输入为0,标准输出为1,标准错误为2。

重定向帮我们将命令输出的内容存入文件当中记录,保持了终端的清爽,避免被大量标准输出淹没,并可以专注于报错信息的处理。但运行一些大型项目时候,其输出的日志信息可能非常多,因此容易造成大量的硬盘读写需求,使得我们的时间被大量浪费在I/O上。对此,Unix shell给出的解法是使用"fake disk"——/dev/null,它就像一个黑洞一样,所有重定向到这个路径的内容都会消失。

Note

tail可以打印文本的指定尾部行。很多生物信息学程序的分析需要耗费一段时间,当我们将两个流都重定向,终端上不会显示有关信息,如果我们想要了解当前状况,tail就是一个不错的选择。tail a.log可以打印文件后十行,tail -f a.log可以实时跟踪被新加入文件的内容。

Standard Input Redirection

在Unix shell中,<是用来重定向标准输入流的,虽然这种应用并不常见。一般来说,标准输入是从键盘产生的,而程序的输入还可以通过管道符|或者命令提供的文件参数来传入。标准输入重定向的应用如下:

$ program < inputfile > outputfile

以上命令将一个人为创建的inputfile输入给程序program,并将其输出重定向到outputfile中。

The Excellent Unix Pipe

管道符|可谓Unix shell命令的灵魂之一,它以简单但好用的功能——将上一个命令的标准输出重定向至下一个命令的标准输入,惊艳到了每一个上手Unix shell的人(比如我)。特别地,管道符并不会重定向标准错误输出流,所以任何警告与报错都会被打印在终端上。

同时,由于管道符对流的传递本身不涉及到读写硬盘,绝大多数操作都在内存中进行,这使得其避免了使用重定向符导致的大量的巨慢无比的硬盘读写,大大加快了一系列命令的执行速度。在大多数情况下,I/O总是最慢的。

Pipe in Action

利用管道符,我们可以快速构建起一个小命令行程序,而无需写一个脚本,来快速执行一些操作,比如检查文件,整合信息等等。我们以一个可能发生的实例来说明管道符的强大,假如我们的程序在处理个FASTA文件时候警告,其中包含有非核酸序列字符,我们该如何快速定位问题呢?

让我们来看看grep|热血沸腾的组合技:

$ grep -v "^>" temp.fasta | grep --color -i "[^ATGC]"

以上命令组成为:

  1. 第一个grep-v代表排除匹配结果,命令含义为“输出temp.fasta中不以'>'开头的行”
  2. 管道符|:将第一个grep的输出重定向到第二个grep的输入
  3. 第二个grep--color会为匹配结果标色,-i表示不区分大小写,[^ATGC]则比较复杂,[]表示匹配其中任意字符,中括号中的^表示取反,所以[^ATGC]的意思为不是A、T、G、C的字符。整个命令含义为“输出标色的不是ATCG中任意一个的字符,且不区分大小写”。

以上命令的输出为:

CCCCAAAGACGGACCAATCCAGCAGCTTCTACTGCTAYCCATGCTCCCCTCCCTTCGCCGCCGCCGACGC

很可惜的是代码块里没办法标色,事实上以上序列中的"Y"被标注上了鲜明的颜色。我非常建议自己创建一个简单的FASTA文件,并亲手试试这个命令的效果。

Note

FASTA 是一种在生物信息学和生物化学领域中广泛使用的、基于文本的格式,用于表示核苷酸序列(例如 DNA 或 RNA)或氨基酸序列(即蛋白质序列)。

Warning

使用grep的时候,用双引号""pattern括起来是一个很好的习惯,这可以避免很多超出预期的行为,比如:

grep -v > temp.fasta
Unix shell会将>解释为重定向而非匹配字符">",这会直接覆盖你的文件!!!

Combining Pipes and Redirection

很多时候,生物信息学程序会同时使用多个流来输出信息,如何处理这些流,提取有用的信息,需要借助重定向符和管道符。

假设我们有两个程序,program1program2,其中program1负责处理input.txt,并且将结果输出到标准输出流,同时将诊断信息输出到标准错误流。程序2接收程序1的标准输出作为输入,并进行处理。程序2同样会将自身的诊断信息输出到标准错误流,并将处理结果输出到标准输出流。

如果我们不恰当处理错误信息,过多的诊断信息会直接刷屏终端,对此,我们用重定向符和通道符来构建命令:

$ program1 input.txt 2> program1.stderr | \
$ program2 2> program2.stderr > result.txt

有时候,我们需要将标准错误流重定向到标准输出流,以便于我们后续的处理,特别是因为|只能将上一个命令的标准输出连接到下一个命令的标准输入,而对错误信息则放任其输出。对此,我们有如下命令:

$ program1 2>&1 | grep "error"

其中2>&1用于将标准错误流重定向到标准输出流。

A Tee In Pipes

管道符避免了不必要的磁盘读写,但是不意味着我们完全不需要中间文件。有些时候,我们期待在程序运行至某一步骤的时候,将其当前的结果与状态信息保存为一个中间文件,同时可以将当前的结果继续传递给下一个命令。实现的方法是tee,它将管道的标准输出流的一个副本重定向到一个中间文件,同时继续通过其标准输出:

$ program1 input.txt 2> program1.stderr | \
$ tee intermediate-file.txt | \
$ program2 2> program2.stderr > result.txt

Managing and Interacting with Processes

在生物信息学领域,我们往往需要运行一个耗时很长的程序,而恰当地掌控这些进程非常重要,本节我们将学习如何在后台运行和管理进程、终止异常进程以及检查进程的退出状态。

Background Processes

在Unix shell中,我们只有在提示符(通常是$或其他)出现的时候才能键入命令,提示符的作用是向用户表明shell已经准备好接受下一个命令了。当我们运行一个耗时很短的简单程序时候,这不是什么问题。但如果是一个耗时超级长的任务,它将会硬控你直到它运行结束,或者被忍无可忍的你强制中断。

通常,你可以选择在终端里新开一个shell,但是这并不优雅,我们可以用&来告诉shell,这个命令在后台执行

$ program1 input.txt > result.txt &
[1] 16322

该命令返回一个序号和一个ID,这个ID(PID)是程序的唯一标识符,可以用来标志这个程序。我们要查询后台程序的状态,可以使用jobs:

[1]  + running    program1 input.txt > result.txt

当我们想将后台程序移至前台,可以使用fg,一般为fg %<num>,其中<num>是对应程序在jobs中返回的序号,不指定时则指向最近的一个后台程序。比如在上面的例子中:

$ fg %1

这个命名将我们刚刚运行的程序移至前台,当然,如果只有一个后台程序,fgfg %1的行为是一致的。

Warning

终端的关闭也会杀死后台程序,每当终端关闭,其就会发送一个挂起信号(SIGNUP),从而停止大部分Unix命令行程序。为了防止我们的后台程序随着终端关闭被杀死,需要在nohup工具或Tmux中运行它们。

同样的,我们也可以将在前台(foreground)运行的程序,切换到后台程序,可以使用bg。想要将前台程序切回后台,首先使用control+Z来暂停进程,然后使用bg切换:

$ program1 input.txt > result.txt
# enter control-z
[1]  + 17332 suspended  program1 input.txt > result.txt
$ bg %1
[1]  + 17332 continued  program1 input.txt > result.txt

Killing Progress

我们可以通过control+C中断程序,其只作用于前台程序,不会影响后台程序。Unix shell还提供了toppskill来管理进程。

Exit Statuses

程序都会有个返回值,用来标定其的运行结果。在Unix standards中,正常退出的状态应该是0,任何非零返回都存在错误,通常这些错误会伴随一个报错信息,被打印在终端上。

Warning

要注意的是,程序的exit status是由程序员自己指定的,实际上,即使是同一个退出状态码,对不同的程序来说含义是不同的。甚至即使有错误,程序也可能返回0,要么是程序员忘记处理错误,要么就是故意而为之。总之,别太相信你的程序

一般来说,Unix shell不会把程序的exit status打印在终端上,其被存在S?的变量中,我们可以用echo $?来查看它。

但如果你想我一样,使用zsh作为默认的shell,并且通过oh-my-zsh进行了配置,那么在你的命令提示符中通常会包含这个exit status。

当我们在链式执行一连串命令的时候,程序的退出状态码是非常有用的,我们可以根据前一个命令的状态码条件性执行下一个命令,Unix shell提供了&&||两种方法来实现,其中:

  • &&:前一个命令执行成功,后一个命令才执行。
  • ||:前一个命令执行失败,后一个命令才执行。

让我们来看一些具体的例子,如果我们想要让后一个程序在前一个程序执行成功后再执行:

$ program1 input.txt > intermediate.txt && \
$ program2 intermediate.txt >result.txt

如果我们想要在程序运行失败的时候给出相应的提示信息:

$ program1 input.txt || \
$ echo "Error: execution failed"

在Unix shell中,truefalse是两个布尔变量,你可以用它来调试你的短路执行语句:

$ true && echo "first command"
"first command"
$ false || echo "second command"
"second command"

特别的,如果你只是想顺序执行一系列命令,而不关心命令的状态,那么你可以用;来分割命令:

$ false; true; false; echo "Nothing happened"
"Nothing happened"

Command Substitution

命令替换是一个Unix shell非常强大的功能,它有点类似于Python中的格式字符串。在一行Unix shell命令中,你可以使用$()来包裹一个命令,这个命令的输出会被当做字符串,用在本行的命令当中。让我们来看看下面这个例子:

$ mkdir result-$(date +%F)

这个命令会创建一个文件夹,名字取决于当前的时间,比如你是在2025-05-27运行这个命令,它会创建一个文件夹result-2025-05-27。在这个命令中,被$()包裹的date +%F会被单独执行,得到的日期返回值会被运用到原命令的替换位置。

Tip

在Unix shell中,提供了alias这么一个别名工具,用来为原本一长串但是很常用的命令取一个方便记忆和使用的别名

想要设置这别名,需要在你的shell的配置文件写入以下内容:

alias today="data +%F"
然后重新加载你的shell配置:
source ~/.zshrc
这样以来,使用today就等价于使用date +%F了。