将输出重定向到文件或程序
目标
完成本节内容后,你能够用shell重定向将输出或错误保存到文件中,并通过多个命令行程序用管道处理命令输出。
标准输入、标准输出和标准错误
一个运行中的程序或进程需要从某个地方读取输入,并将输出写到某个地方。从shell提示符中运行的命令通常从键盘上读取输入,并将其输出发送到终端窗口。
一个进程使用称为文件描述符的编号通道来获取输入和发送输出。所有的进程都从至少三个文件描述符开始。标准输入(通道0)从键盘上读取输入。标准输出(通道1)向终端发送正常输出。标准错误(通道2)向终端发送错误信息。如果程序打开单独的连接到其他文件,可能会使用编号较高的文件描述符。
通道(文件描述符)
编号 | 通道名称 | 描述 | 默认连接 | 使用方法 |
---|---|---|---|---|
0 | stdin | 标准输入 | 键盘 | 只读 |
1 | stdout | 标准输出 | 终端 | 只写 |
2 | stderr | 标准错误 | 终端 | 只写 |
3 | filename | 其他文件 | 无 | 读写 |
重定向输出到文件
I/O重定向改变了进程获取输入或输出的方式。进程不是从键盘上获取输入,或者将输出和错误发送到终端,而是从文件中读取或写入文件。重定向可以让您将消息保存到通文件中。另外,您还可以使用重定向来丢弃输出或错误,这样就不会在终端上显示或保存它们。
重定向 stdout 可以抑制进程输出在终端上显示。从下表中可以看出,只重定向stdout并不能抑制在终端上显示stderr错误信息。如果该文件不存在,则会被创建。如果该文件确实存在,该文件的内容将被覆盖。
如果你想丢弃消息,特殊文件/dev/null会悄悄地丢弃重定向到它的通道输出,并且始终是一个空文件。
输出重定向运算符
3
使用方法 | 说明 | 图示 |
---|---|---|
> file | 重定向stdout覆盖文件 | |
>> file | 重定向stdout附加在文件尾部 | |
2> file | 重定向stderr覆盖文件 | |
2> /dev/null | 重定向到/dev/null来丢弃stderr错误信息。 | |
> file 2>&1 &> file |
重定向stdout和stderr,覆盖同一文件 | |
>> file 2>&1 &>> file |
将stdout和stderr重定向到附加到同一个文件中 | |
重定向操作的顺序很重要。下面的顺序是将标准输出重定向到文件,然后将标准错误重定向到与标准输出(文件)相同的地方。
> file 2>&1
然而,下一个顺序做了相反的重定向。这是将标准错误重定向到标准输出的默认位置(终端窗口,所以没有变化),然后只将标准输出重定向到文件:
&>file 代替 >file 2>&1
&>>file 代替 >>file 2>&1 (在 Bash4/RHEL6之后)
其他的系统管理员和程序员也在使用其他与bash相关的shell(被称为Bourne兼容的shell)进行脚本命令,他们认为应该避免使用较新的合并重定向操作符,因为这些操作符并没有在所有这些shell中标准化或实现,而且还有其他的限制。
本课程的作者对这个话题采取了中立的立场,这两种语法在实战中都有可能遇到。
输出重定向示例
许多日常管理任务都是通过使用重定向来简化的。在考虑下面的例子时,请使用上表提供帮助。
-
保存一个时间戳,以便日后参考。
[user@host ~]$ date > /tmp/saved-timestamp
-
将日志文件中的最后100行复制到另一个文件中。
[user@host ~]$ tail -n 100 /var/log/dmesg > /tmp/last-100-boot-messages
-
将四个文件串联成一个。
[user@host ~]$ cat file1 file2 file3 file4 > /tmp/all-four-in-one
将主目录中的隐藏文件名和普通文件名列成一个文件。
[user@host ~]$ ls -a > /tmp/my-file-names
-
将输出添加到现有文件中。
[user@host ~]$ echo "new line of information" >> /tmp/many-lines-of-information [user@host ~]$ diff previous-file current-file >> /tmp/tracking-changes-made
-
接下来的几个命令会产生错误信息,因为有些系统目录对正常用户来说是无法访问的。当错误信息被重定向时,请注意观察。在终端上查看正常的命令输出时,将错误重定向到一个文件。
[user@host ~]$ find /etc -name passwd 2> /tmp/errors
-
将过程输出和错误信息保存到单独的文件中。
[user@host ~]$ find /etc -name passwd > /tmp/output 2> /tmp/errors
-
忽略和丢弃错误信息。
[user@host ~]$ find /etc -name passwd > /tmp/output 2> /dev/null
-
将输出和生成的错误存储在一起。
[user@host ~]$ find /etc -name passwd &> /tmp/save-both
-
将输出和生成的错误添加到现有文件中。
[user@host ~]$ find /etc -name passwd >> /tmp/save-both 2>&1
管道
管道是一个由一个或多个命令组成的序列,由管道字符(|)隔开。管道将第一个命令的标准输出与下一个命令的标准输入连接起来。
管道允许一个进程的输出在输出到终端之前,由其他进程对其进行操作和格式化。一个有用的思维形象是想象数据从一个进程到另一个进程的管道中 "流动",并被流经的管道中的每一条命令稍作改变。
管道示例
本例采用ls命令的输入,使用less在终端上显示。
[user@host ~]$ ls -l /usr/bin | less
ls命令的输出被传送到wc -l,它计算从ls接收到的行数,并将其打印到终端。
[user@host ~]$ ls | wc -l
head将从ls -t输出前10行输出,最后的结果将被重定向到一个文件。
[user@host ~]$ ls -t | head -n 10 > /tmp/ten-last-changed-files
管道、重定向和tee命令
当重定向与管道结合在一起时,shell会先设置整个管道,然后再重定向输入/输出。如果在管道中使用输出重定向,那么输出将进入文件,而不是进入管道中的下一个命令。
在这个例子中,ls命令的输出会进入文件,终端上什么也不显示。
[user@host ~]$ ls > /tmp/saved-output | less
tee命令克服了这个限制。在管道中,tee会将其标准输入复制到标准输出,同时也会将其标准输出重定向到作为命令参数命名的文件。如果你把数据想象成流经管道的水,tee可以被形象化为管道中的一个 "T "型接头,它可以将输出导向两个方向。
使用tee命令的管道示例
这个例子将 ls 命令的输出重定向到文件中,并将其传递给less,以便在终端上逐一显示。
[user@host ~]$ ls -l | tee /tmp/saved-output | less
如果在管道的结尾使用tee,则可以将命令的最终输出到显示在终端并保存。
[user@host ~]$ ls -t | head -n 10 | tee /tmp/ten-last-changed-files
标准错误可以通过管道重定向,但合并重定向操作符(&>和&>>)不能用来做这个操作。
以下是通过管道重定向标准输出和标准错误的正确方法:
[user@host ~]$ find -name / passwd 2>&1 | less
从shell提示符编辑文本文件
目标
完成本节内容后,你应该能够使用vim编辑器从命令行创建和编辑文本文件。
用vim编辑文件
Linux的一个重要设计原则是,信息和配置设置通常存储在基于文本的文件中。这些文件可以以各种方式结构化,如设置列表、类似INI的格式、结构化的XML或YAML等。但是,文本文件的优点是可以用任何简单的文本编辑器来查看和编辑。
Vim是vi编辑器的改进版,它是Linux和UNIX系统中发行的vi编辑器的改进版。Vim具有很强的可配置性和高效性,对实践用户来说,它包括了分屏编辑、颜色格式化和文本编辑高亮等功能。
为什么要学习Vim?
你应该知道如何使用至少一个可以从纯文本的shell提示符中使用的文本编辑器。如果你知道,你可以从终端窗口编辑基于文本的配置文件,也可以通过ssh或Web控制台远程登录来编辑。那么你就不需要访问图形化桌面来编辑服务器上的文件,事实上,服务器可能根本不需要运行图形化桌面环境。
为什么要学习Vim而不是其他可能的选择呢?关键的原因是,如果有文本编辑器的话,Vim几乎都会安装在服务器上。这是因为vi是由POSIX标准规定的,Linux和许多其他类似UNIX的操作系统在很大程度上都符合POSIX标准。
此外,Vim经常被用作其他常见操作系统或发行版上的vi实现。例如,macOS目前默认包含了Vim的轻量级安装。所以,在Linux中学习到的Vim技能可能也会帮助你在其他地方完成一些事情。
启动Vim
Vim 以两种不同的方式安装在 Red Hat Enterprise Linux 中,可能会影响你可用的功能和Vim命令。
你的服务器可能只安装了vim-minimal软件包。这是一个非常轻量级的安装,只包括核心功能集和基本的vi命令。在这种情况下,您可以用vi filename打开一个文件进行编辑,本节中讨论的所有核心功能都可以使用。
另外,您的服务器可能安装了vim增强版的软件包。这提供了一个更全面的功能,一个在线帮助系统和一个教程程序。在这个增强模式下启动Vim,你可以使用vim命令。
[user@host ~]$ vim filename
无论哪种方式,我们在本节中讨论的核心功能都将与这两个命令一起使用。
如果安装了vim-enhanced,普通用户将有一个shell别名设置,这样如果他们运行vi命令,就会自动得到vim命令。这并不适用于root和其他UID低于200的用户(系统服务使用的UID)。
如果你是以root用户的身份编辑文件,而你希望vi能在增强模式下运行,这可能会让你大吃一惊。同样的,如果安装了vim-enhanced,而普通用户由于某种原因想要使用简单的vi,他们可能需要使用 \vi来暂时覆盖别名。
高级用户可以使用 \vi --version和vim --version来比较这两个命令的特征集。
Vim操作模式
Vim的一个不寻常的特点是它有多种操作模式,包括命令模式、扩展命令模式、编辑模式和视觉模式。根据不同的模式,您可能会发布命令、编辑文本或处理文本块。作为一个新的Vim用户,你应该时刻注意你当前的模式,因为在不同的模式下按键的效果不同。
当你第一次打开Vim时,它以命令模式启动,用于导航、剪切和粘贴以及其他文本操作。使用单字符键进入其他模式中的每一个模式,以访问特定的编辑功能。
- 按i键进入插入模式,所有输入的文本都会变成文件内容。按Esc键返回到命令模式。
- v键进入视觉模式,可选择多个字符进行文本操作。使用Shift+V来选择多行,Ctrl+V来选择块。进入视觉模式时使用相同的按键(v、Shift+V或Ctrl+V)退出。
- 按 :键开始扩展命令模式,用于写文件(保存文件)和退出 Vim 编辑器等任务。
最基本的Vim工作流程
Vim具有高效、协调的按键功能,可用于高级编辑任务。虽然在实践中被认为很有用,但Vim的功能可能会让新用户不知所措。
i键会使Vim进入插入模式。在这之后输入的所有文本都被视为文件内容,直到您退出插入模式。Esc键退出插入模式,并将Vim返回到命令模式。u键将撤销最近的编辑。按 x 键可删除单个字符。:w 命令会写入(保存)文件,并保持在命令模式下进行更多的编辑。:wq 命令会写入(保存)文件,并退出 Vim。:q!命令退出Vim,丢弃上次写入后的所有文件更改。Vim用户必须学习这些命令才能完成任何编辑任务。
重新排列现有文本
在Vim中,复制和粘贴被称为yank 和 put,使用命令字符Y和P。使用方向键展开视觉选择。准备好后,按Y键将选区拉到内存中。将光标定位在新的位置,然后按P键将选区放在光标处。
Vim中的视觉模式
视觉模式是一个很好的突出显示和操作文字的方法。有三个按键:
- 字符模式:v
- 行模式:Shift+V
- 块模式:Ctrl+V
Vim有很多功能,但你应该先掌握基本的工作流程。你不需要快速了解整个编辑器及其功能。通过练习来熟悉这些基础知识,然后你可以通过学习额外的Vim命令(按键)来扩展你的Vim词汇量。
改变shell环境
目标
完成本节内容后,你能够设置shell变量来帮助运行命令,并能编辑Bash启动脚本来设置shell和环境变量来修改shell的行为和从shell运行的程序。
使用shell变量
Bash shell允许你设置shell变量,你可以用它来帮助运行命令或修改shell的行为。你也可以将shell变量导出为环境变量,当启动时,这些变量会自动复制到从该shell运行的程序中。你可以使用变量来帮助运行一个带长参数的命令,或者将常用的设置应用到从该shell运行的命令中。
Shell变量是一个特定的shell会话所独有的。如果你有两个终端窗口打开,或者是两个独立的远程服务器的登录会话,那么你正在运行两个shell。每个shell都有自己的shell变量的值。
为变量赋值
使用下面的语法给一个shell变量赋值:
VARIABLENAME=value
变量名可以包含大写或小写字母、数字和下划线字符(_)。例如,下面的命令可以设置shell变量:
[user@host ~]$ COUNT=40
[user@host ~]$ first_name=John
[user@host ~]$ file1=/tmp/abc
[user@host ~]$ _ID=RH123
请记住,这个更改只影响到您运行该命令的shell,不会影响到本服务器上运行的其他shell。
你可以使用set命令来列出所有当前被设置的shell变量。(它还会列出所有的shell函数,你可以忽略他们。)这个列表足够长,你可能会想把输出shell命令中,这样你就可以一页一页地查看。
[user@host ~]$ set | less
BASH=/usr/bin/bash
BASHOPTS=checkwinsize:cmdhist:complete_fullquote:expand_aliases:extglob:extquote:
force_fignore:histappend:interactive_comments:progcomp:promptvars:sourcepath BASHRCSOURCED=Y
...output omitted...
使用变量扩展检索值
你可以使用变量扩展来引用你所设置的变量的值。要做到这一点,在变量的名称前加上一个美元符号($)。在下面的例子中,echo命令打印出输入的命令行的其余部分,但在执行了变量扩展之后。
例如,下面的命令将变量COUNT设置为40。
[user@host ~]$ COUNT=40
如果你输入命令 echo COUNT,它将打印出字符串 COUNT。
[user@host ~]$ echo COUNT
COUNT
但如果你输入命令echo $COUNT,它就会打印出变量COUNT的值。
[user@host ~]$ echo $COUNT
40
一个更实用的例子可能是使用一个变量来引用多个命令的长文件名。
[user@host ~]$ file1=/tmp/tmp.z9pXW0HqcC
[user@host ~]$ ls -l $file1
-rw-------. 1 student student 1452 Jan 22 14:39 /tmp/tmp.z9pXW0HqcC
[user@host ~]$ rm $file1
[user@host ~]$ ls -l $file1
total 0
如果变量名旁边有任何尾部字符,你可能需要用大括号来保护变量名。你可以在变量扩展中始终使用大括号,但你也会看到很多不需要大括号的例子,在这些例子中,你可以省略掉大括号。
在下面的示例中,第一个回波命令试图扩展不存在的变量COUNTx,这并没有导致错误,而是返回了空。
[user@host ~]$ echo Repeat $COUNTx
Repeat
[user@host ~]$ echo Repeat ${COUNT}x
Repeat 40x
使用Shell变量配置Bash
有些shell变量是在Bash启动时设置的,但可以通过修改来调整shell的行为。
例如,影响shell历史记录和history命令的两个shell变量是HISTFILE和HISTFILESIZE。如果设置了HISTFILE,它指定了shell历史记录的保存位置。默认情况下是用户的~/.bash_history文件。HISTFILESIZE变量指定了从历史记录中保存在该文件中的命令数量。
另一个例子是PS1,它是一个控制shell提示符外观的shell变量。如果你改变这个值,它将改变你的shell提示符的外观。在bash(1)man page的 "PROMPTING "一节中列出了一些特殊字符的扩展。
[user@host ~]$ PS1="bash\$ "
bash$ PS1="[\u@\h \W]\$ "
[user@host ~]$
关于上面的例子有两点需要注意:第一,由于PS1设置的值是一个提示符,所以在提示符的结尾几乎总是以尾部的空格结束。第二,当变量的值包含某种形式的空格,包括空格、制表符或回车符时,值必须用双引号包围,可以是单引号,也可以是双引号;这不是可选的。如果省略了引号,就会出现意想不到的结果。请看上面的PS1例子,注意它既符合建议(尾部空格),也符合规则(引号)。
用环境变量配置程序
shell为你从该shell中运行的程序提供了一个环境。其中,这个环境包括文件系统中当前工作目录的信息、传递给程序的命令行选项以及环境变量的值。程序可以使用这些环境变量来改变它们的行为或默认设置。
非环境变量的shell变量只能由shell使用。环境变量可以被shell和从该shell运行的程序使用。
你可以通过使用 export 命令将shell中定义的任何变量标记成环境变量。
[user@host ~]$ EDITOR=vim
[user@host ~]$ export EDITOR
你可以一步到位地设置和导出一个变量:
[user@host ~]$ export EDITOR=vim
应用程序和会话使用这些变量来决定它们的行为。例如,shell在启动时自动将HOME变量设置为用户主目录的文件名。
另一个例子是LANG,它设置了locale。它可以调整程序输出的首选语言;字符集;日期、数字和货币的格式化;以及程序的排序顺序。如果设置为en_US.UTF-8,则使用UTF-8 Unicode字符编码的美国英语。如果设置为其他的东西,例如fr_FR.UTF-8,它将使用法国的UTF-8 Unicode字符编码。
[user@host ~]$ date
Tue Jan 22 16:37:45 CST 2019
[user@host ~]$ export LANG=fr_FR.UTF-8
[user@host ~]$ date
mar. janv. 22 16:38:14 CST 2019
另一个重要的环境变量是PATH。PATH变量包含一个包含程序的冒号分隔的目录列表:
[user@host ~]$ echo $PATH /home/user/.local/bin:/home/user/bin:/usr/share/Modules/bin:/usr/local/bin:/usr/ bin:/usr/local/sbin:/usr/sbin
当你运行ls这样的命令时,shell会依次在每个目录中寻找可执行文件ls,然后运行第一个匹配的文件。(在一个典型的系统中,这是 /usr/ bin/ls。)
你可以轻松地在PATH的末尾添加额外的目录。例如,也许你有一些可执行程序或脚本,你想在 /home/user/sbin 中像普通命令一样运行。你可以把/home/user/sbin添加到当前会话的PATH末尾,就像这样:
[user@host ~]$ export PATH=${PATH}:/home/user/sbin
要列出一个特定shell的所有环境变量,请运行env命令。
[user@host ~]$ env
...output omitted...
LANG=en_US.UTF-8
HISTCONTROL=ignoredups
HOSTNAME=host.example.com
XDG_SESSION_ID=4
...output omitted...
设置默认文本编辑器
EDITOR 环境变量指定了你要使用的程序作为命令行程序的默认文本编辑器。如果没有指定的话,很多程序都会使用vi或vim,但如果需要的话,你可以覆盖这个首选项:
[user@host ~]$ export EDITOR=nano
自动设置变量
如果你想在 shell 启动时自动设置 shell 或环境变量,你可以编辑 Bash 启动脚本。当Bash启动时,会运行几个包含shell命令的文本文件,这些文件会初始化shell环境。
具体运行的脚本取决于shell是如何启动的,是交互式登录shell、交互式非登录shell还是shell脚本。
假设默认的 /etc/profile、/etc/bashrc 和 ~/.bash_profile 文件,如果你想对你的用户帐户进行修改,并在启动时影响到所有交互式 shell 提示,请编辑 ~/.bashrc 文件。例如,你可以将该帐户的默认编辑器设置为nano,将文件编辑为读:
# .bashrc
# Source global definitions
if [ -f /etc/bashrc ]; then
. /etc/bashrc
fi
# User specific environment
PATH="$HOME/.local/bin:$HOME/bin:$PATH"
export PATH
# User specific aliases and functions
export EDITOR=nano
取消设置和取消导出的变量
要完全取消设置和取消导出一个变量,请使用 unset 命令。
[user@host ~]$ echo $file1
/tmp/tmp.z9pXW0HqcC
[user@host ~]$ unset file1
[user@host ~]$ echo $file1
[user@host ~]$
要取消导出一个变量而不设置它,请使用 export -n 命令:
[user@host ~]$ export -n PS1
总结
- 运行中的程序或进程有三个标准的通信通道,标准输入、标准输出和标准错误。
- 你可以使用I/O重定向来读取文件中的标准输入,或将进程的输出或错误写到文件中。
- 管道可以用来连接一个进程的标准输出和另一个进程的标准输入,并可以用来格式化输出或建立复杂的命令。
- 你应该知道如何使用至少一个命令行文本编辑器,一般都会安装Vim。
- Shell变量可以帮助你运行命令,而且是特定的shell会话所特有的。
- 环境变量可以帮助你配置shell的行为或它启动的进程。