云网牛站
所在位置:首页 > Linux培训 > 第3章 管道符、重定向与环境变量

第3章 管道符、重定向与环境变量

2017-10-26 14:57:05作者:刘遄稿源:Linux就该这么学

Linux就该这么学第3章 管道符、重定向与环境变量概述

虽然此时同学们已经掌握了数十个常用的Linux系统命令,但如果不能把各个命令通过语法结合使用,绝对会直线拉低工作效率。本章节首先学习Linux命令与文件读写操作有关的重定向技术的五种模式——标准覆盖输出重定向、标准追加输出重定向、错误覆盖输出重定向、错误追加输出重定向以及输入重定向,让读者通过实验真切的理解每个重定向的作用,解决输出信息的保存问题。然后深入学习管道命令符,帮助读者理解命令与命令之间的搭配使用方法,进一步提高命令输出值的处理效率。最后通过学习Linux系统命令行中的通配符和常见转义符,让您输入的Linux命令表达的更加准确,为下一章学习编写Shell脚本打好功底。通过深度剖析Bash解释器执行Linux命令的内部原理,为读者引入学习PATH变量及Linux系统中重要环境变量的作用。

 

本章目录结构

3.1.输入输出重定向

3.2.管道命令符

3.3.命令行的通配符

3.4.常用的转义字符

3.5.重要的环境变量

 

3.1.输入输出重定向

既然同学们在上一个章节中已经学完了几乎所有基础且常用的Linux命令,那么接下来的学习目标就是要把多个Linux命令合适的结合到一起工作,使得咱们的Linux系统在处理数据时更加的高效。而要做到这一点,就特别有必要搞明白命令的输入和输出重定向原理,简单用一句话来概括即“使用输入重定向能够把文件导入到命令中,而输出重定向则是能够把原本要输出到屏幕的数据信息写入到指定文件中”。因为在日常的学习和工作中使用到输出重定向会稍微多一些,所以细分下又有了标准输出重定向和错误输出重定向两种不同的技术以及清空写入与追加写入两种模式,听起来就很玄妙,刘遄老师接下来慢慢与读者道来。

标准输入(STDIN,文件描述符为0):默认从键盘输入,为0时表示是从其他文件或命令的输入。

标准输出(STDOUT,文件描述符为1):默认输出到屏幕,为1时表示是文件。

错误输出(STDERR,文件描述符为2):默认输出到屏幕,为2时表示是文件。

比如咱们分别查看两个文件的属性信息,而第二个文件是不存在的,这样虽然都会在屏幕上输出一些数据信息,但其实差异很大:

[root@linuxprobe ~]# touch linuxprobe

[root@linuxprobe ~]# ls -l linuxprobe 

-rw-r--r--. 1 root root 0 Aug 5 05:35 linuxprobe

[root@linuxprobe ~]# ls -l xxxxxx

ls: cannot access xxxxxx: No such file or directory

第一个叫做linuxprobe的文件是存在的,输出信息是该文件的一些相关属性、权限、大小等信息,也是该命令的标准输出信息,而第二个叫做xxxxxx的文件则是不存在的,输出文件的不存在报错提示信息也是该命令的错误输出信息,那么要想把原本输出到屏幕上的数据转向写入到文件当中,就要区别对待这两种输出信息才行。

对于输入重定向有这些情况:

第3章 管道符、重定向与环境变量

对于输出重定向符有这些情况:

第3章 管道符、重定向与环境变量

先来小试牛刀下吧,通过标准输出重定向将“man bash”命令原本要输出到屏幕的信息写入到文件中去,效果类似于:

[root@linuxprobe ~]# man bash > readme.txt

[root@linuxprobe ~]# cat readme.txt 

BASH(1)   General Commands Manual   BASH(1)

NAME

bash - GNU Bourne-Again SHell

SYNOPSIS

bash [options] [file]

COPYRIGHT

Bash is Copyright (C) 1989-2011 by the Free Software Foundation, Inc.

DESCRIPTION

Bash  is  an  sh-compatible  command language interpreter that executes

commands read from the standard input or from a file.  Bash also incor‐

porates useful features from the Korn and C shells (ksh and csh).

Bash  is  intended  to  be a conformant implementation of the Shell and

Utilities portion  of  the  IEEE  POSIX  specification  (IEEE  Standard

1003.1).  Bash can be configured to be POSIX-conformant by default.

………………省略部分输出信息………………

有没有感觉到特别方便呢?那么接下来试试输出重定向技术中的清空写入与追加写入两种不同模式带来的变化吧~先通过清空模式向文件写入一行数据(该文件中包含上一个实验的man命令信息),然后再通过追加模式向文件再写入一次数据,最终咱们看到的文件内容会是这个样子的:

[root@linuxprobe ~]# echo "Welcome to LinuxProbe.Com" > readme.txt

[root@linuxprobe ~]# echo "Quality linux learning materials" >> readme.txt

[root@linuxprobe ~]# cat readme.txt

Welcome to LinuxProbe.Com

Quality linux learning materials

虽然都是输出重定向技术,但对于不同命令的标准输出和错误输出还都有点区别,例如查看下当前目录中某个文件的信息吧。因为这个文件是真实存在的,因此使用标准输出即可将数据写入到文件中,而错误的输出重定向则不行,依然会把信息输出到了屏幕上。

[root@linuxprobe ~]# ls -l linuxprobe 

-rw-r--r--. 1 root root 0 Mar  1 13:30 linuxprobe

[root@linuxprobe ~]# ls -l linuxprobe > /root/stderr.txt 

[root@linuxprobe ~]# ls -l linuxprobe 2> /root/stderr.txt 

-rw-r--r--. 1 root root 0 Mar  1 13:30 linuxprobe

那如果是想把命令的报错信息写入到文件呢?例如当您在执行一个自动化的Shell脚本时会特别的实用,因为可以通过把整个脚本执行过程中的报错信息都记录到文件中,便于安装后的排错工作。接下来学习实践中咱们就以一个不存在的文件做演示吧:

[root@linuxprobe ~]# ls -l xxxxxx 

cannot access xxxxxx: No such file or directory

[root@linuxprobe ~]# ls -l xxxxxx > /root/stderr.txt

cannot access xxxxxx: No such file or directory

[root@linuxprobe ~]# ls -l xxxxxx 2> /root/stderr.txt

[root@linuxprobe ~]# cat /root/stderr.txt 

ls: cannot access xxxxxx: No such file or directory

而输入重定向则显得有些冷门,在工作中遇到的机会相对少一点,作用是把文件直接导入到命令中。接下来使用输入重定向把文件导入给“wc -l”命令来统计下内容行数吧,这样命令其实等同于接下来要学习的“cat readme.txt | wc-l”的管道符命令组合。

[root@linuxprobe ~]# wc -l < readme.txt

2

 

3.2.管道命令符

细心认真的读者肯定留意到了在学习第2章6小节的tr命令时见到的一个叫做管道符的东西,输入方法是同时按下键盘的“Shift”与“\”键,执行格式为“命令A | 命令B”,其实管道命令符的作用也能用一句话来概括:“把前一个命令原本要输出到屏幕的数据当作是后一个命令的标准输入”。回想前面第2章8小节学习过的grep文本搜索命令通过匹配关键词/sbin/nologin找出了所有被限制登录系统的用户,其实只要学完了这个小节,完全可以把下面的两条命令合并到一起。

找出被限制登录用户的命令是:grep "/sbin/nologin" /etc/passwd

统计文本行数的命令则是:wc -l

现在要做的就是把搜索命令的输出值传递给统计命令,即是把原本要输出到屏幕的用户信息列表再交给wc命令做进一步的加工,因此只需要把管道符放到两条命令之间即可,这简直是太方便了。

[root@linuxprobe ~]# grep "/sbin/nologin" /etc/passwd | wc -l

33

学习到了这个管道符就像拿到了一个法宝,让咱们来套用到其他不同的命令上吧,比如用翻页的形式查看/etc目录中的文件列表及属性信息吧(默认会一股脑的都显示到屏幕上,根本看不清楚):

[root@linuxprobe ~]# ls -l /etc/ | more

total 1400

drwxr-xr-x. 3 root root 97 Jul 10 17:26 abrt

-rw-r--r--. 1 root root 16 Jul 10 17:36 adjtime

-rw-r--r--. 1 root root 1518 Jun 7 2013 aliases

-rw-r--r--. 1 root root 12288 Jul 10 09:38 aliases.db

drwxr-xr-x. 2 root root 49 Jul 10 17:26 alsa

drwxr-xr-x. 2 root root 4096 Jul 10 17:31 alternatives

-rw-------. 1 root root 541 Jan 28 2017 anacrontab

-rw-r--r--. 1 root root 55 Jan 29 2017 asound.conf

-rw-r--r--. 1 root root 1 Jan 29 2017 at.deny

drwxr-xr-x. 2 root root 31 Jul 10 17:27 at-spi2

drwxr-x---. 3 root root 41 Jul 10 17:26 audisp

drwxr-x---. 3 root root 79 Jul 10 17:37 audit

drwxr-xr-x. 4 root root 94 Jul 10 17:26 avahi

--More--

平时修改用户密码需要输入两次密码进行确认才能行,这在编写自动化脚本时绝对会成为非常致命的缺陷,而咱们可以通过把管道符和passwd命令的--stdin参数相结合,用一条命令即可完成密码重置操作:

[root@linuxprobe ~]# echo "linuxprobe" | passwd --stdin root

Changing password for user root.

passwd: all authentication tokens updated successfully.

对于这个管道符命令是不是觉得有些相见恨晚?其实玩法还有很多,比如默认发送邮件需要交互式的进行才行,而此时则可以通过一条结合了管道符的命令语句把编辑好的内容与标题一起的“打包”,最终用一条命令就顺利的给用户发送了邮件。

[root@linuxprobe ~]# echo "Content" | mail -s "Subject" linuxprobe

[root@linuxprobe ~]# su - linuxprobe

Last login: Fri Jul 10 09:44:07 CST 2017 on :0

[linuxprobe@linuxprobe ~]$ mail

Heirloom Mail version 12.5 7/5/10. Type ? for help.

"/var/spool/mail/linuxprobe": 1 message 1 new

>N 1 root Sun Aug 30 17:33 18/578 "Subject"

新手朋友看到上面组合的命令肯定已经觉得很复杂了,但还有很多读者们大呼不过瘾,问能不能让这样方便的命令写的更高级一些呢?比如让内容可以再多写几行并用上前面学习的重定向技术,为了满足大家对《Linux就该这么学》的支持,刘遄老师当然义不容辞的把技术拱手奉上。下面这条自造命令就是通过把mail邮件命令与输入重定向的分界符来结合使用,效果是让用户可以一直的输入内容,直到系统遇到匹配上了用户定义的分界符才最终结束。

[root@linuxprobe ~]# mail -s "Readme" root@linuxprobe.com << over

> I think linux is very practical

> I hope to learn more

> can you teach me ?

> over

[root@linuxprobe ~]#

当然同学们可不要误解管道命令符只能用一次哦,完全可以这样用:“命令A|命令B|命令C”。另外为了进一步方便学生们去记忆、理解管道符这个东西,刘遄老师在讲课的时候经常会把管道符描述成“任意门”,小时候大家也一定看过哆啦a梦吧,机器猫经常为了大雄而从口袋中掏出一件件宝贝,好多次就用到了任意门这个道具,有一集大雄为了邀请朋友们来家玩,于是用任意门穿越到了火星上面,还把家里的水管放到了火星上,那么管道符就好像是数据用于穿越的任意门,帮助咱们提高工作效率,完成之前不敢想象的复杂工作。

 

3.3.命令行的通配符

人们有些时候会拿起了笔却忘了字,明明一个文件的名称就在嘴边却就是想不起来,如果此时需要遍历查找出以某个关键词开头的所有文件该如何操作呢?又例如要想批量查看所有硬盘文件的相关权限属性,笨笨的命令会是这样的:

[root@linuxprobe ~]# ls -l /dev/sda

brw-rw----. 1 root disk 8, 0 May 4 15:55 /dev/sda

[root@linuxprobe ~]# ls -l /dev/sda1

brw-rw----. 1 root disk 8, 1 May 4 15:55 /dev/sda1

[root@linuxprobe ~]# ls -l /dev/sda2

brw-rw----. 1 root disk 8, 2 May 4 15:55 /dev/sda2

[root@linuxprobe ~]# ls -l /dev/sda3

ls: cannot access /dev/sda3: No such file or directory

幸亏我的硬盘文件和分区只有3个,要是有几百个的话,估计一天的工作时间都要忙活这个事了。虽然在未来的第6章才会讲Linux系统的存储结构和FHS协议规范,但其实咱们此时已经能看出一些简单规律了,比如这些硬盘设备文件共性都是以sda开头并且存放到了/dev目录中,那即便不知道分区编号和具体分区的个数也一样可以用通配符来搞定。通配符顾名思义就是通用的匹配信息的符号,比如星号(*)就是代表匹配零个或多个字符,问号(?)是代表匹配单个字符,中括号内加上数字[0-9]代表匹配单个阿拉伯数字的字符,而中括号内加上字母[abc]则是代表匹配单个指定的英文字母。俗话讲百闻不如一见,看书不如做实验,例如匹配下所有在/dev目录中且以sda开头的文件吧:

[root@linuxprobe ~]# ls -l /dev/sda*

brw-rw----. 1 root disk 8, 0 May 4 15:55 /dev/sda

brw-rw----. 1 root disk 8, 1 May 4 15:55 /dev/sda1

brw-rw----. 1 root disk 8, 2 May 4 15:55 /dev/sda2

如果只需要看sda后面一定要有个字符的文件相关信息呢?那就要用到问号来通配了。

[root@linuxprobe ~]# ls -l /dev/sda?

brw-rw----. 1 root disk 8, 1 May 4 15:55 /dev/sda1

brw-rw----. 1 root disk 8, 2 May 4 15:55 /dev/sda2

您除了可以用[0-9]来通配所有的单个阿拉伯数字,也可以用[135]这样的方式仅匹配这三个指定数字,若没有通配到即不会显示出来:

[root@linuxprobe ~]# ls -l /dev/sda[0-9]

brw-rw----. 1 root disk 8, 1 May 4 15:55 /dev/sda1

brw-rw----. 1 root disk 8, 2 May 4 15:55 /dev/sda2

[root@linuxprobe ~]# ls -l /dev/sda[135]

brw-rw----. 1 root disk 8, 1 May 4 15:55 /dev/sda1

 

3.4.常用的转义字符

Shell解释器为了能够更好的理解您想表达的意思,还提供了特别丰富的转义符号来帮助程序员处理输入的特殊数据,为遵循您手中这本《Linux就该这么学》的创作主旨,刘遄老师愣是结合了多年工作学习经验,用了两周时间为读者从数十个转义符中提炼出了四个最常用符号!这也让我深刻的反省了很长时间,原本认为书籍能写的越厚越肯定是大神级人物的这种多年错误观念,希望读者您在读完本书后也能够体会到刘遄老师的这份用心和付出。常见的转义字符包括有:反斜杠(\)的作用就是转义后面的一个变量变为单纯的字符串,单引号('')则是转义其中所有的变量为单纯的字符串,而双引号("")是保留其中的变量属性不转义,反引号(``)则是把其中的命令执行后返回一个结果。

例如咱们先定义一个名称为PRICE的变量并赋值为5,然后通过双引号括起来输出字符串与变量结合的结果:

[root@linuxprobe ~]# PRICE=5

[root@linuxprobe ~]# echo "Price is $PRICE"

Price is 5

[root@linuxprobe ~]# echo "Price is $$PRICE"

Price is 3767PRICE

原本刚刚是希望能够进一步输出“Price is $5”即价格是五美元的字符串信息,但碰巧美元符号与变量提取符号冲突了,因此输出的并不是预想的信息。需要用转义符把第一个$符号转换成单纯的字符串,再或者把整段都转义成单纯的字符串吧(当然这个只是让您看下效果,并不符合实验需要):

[root@linuxprobe ~]# echo "Price is \$$PRICE"

Price is $5

[root@linuxprobe ~]# echo 'Price is \$$PRICE'

Price is \$$PRICE

好啦,咱们对转义符学习的非常顺利,但最后一个您可能看到结果时会觉得很无用,因此暂且先不用管具体的使用场景,就当作是提前为第4章的SHELL编程知识学习做一点小小的铺垫吧。如果只需要某个命令的返回输出值时,就可以用像`命令`这样用反引号括起来的命令格式来达到效果,例如通过反引号与uname -a命令结合通过返回值来查看下本机版本和内核信息吧:

[root@linuxprobe ~]# echo `uname -a`

Linux linuxprobe.com 3.10.0-123.el7.x86_64 #1 SMP Mon May 5 11:16:57 EDT 2017 x86_64 x86_64 x86_64 GNU/Linux

 

3.5.重要的环境变量

变量是计算机系统中用于保存可变值的数据类型,在Linux系统中一般变量名称都是大写的,这仅算是一种约定俗成的规范,平时可以直接通过变量名称来提取到对应的变量值。Linux系统中的环境变量是用来指定系统运行环境的一些参数,比如每个用户不同的家目录、邮件保存存放位置等等。细心的读者一定发现了上一个小节与本小节都分别加了形容词——常见的、重要的,原因其实不言而喻,因为为了通过环境变量帮助Linux系统构建起能够为用户提供服务的工作运行环境,是需要数百个变量协同完成的,您当然没有必要去把每一个变量都看一遍,而应该在最宝贵的书籍中为读者精讲最重要的内容。比如前面小节中提到的一个概念——即Linux系统中一切都是文件,因此Linux命令肯定也不例外,那当用户执行了一条命令之后到底发生了什么事情呢?简单来说就是四个步骤:

第1步骤阶段是判断用户是否以绝对路径或相对路径的方式输入命令(如/bin/ls),如果是的话则直接执行。

第2步骤阶段是检查用户输入的命令是否为“别名命令”,即用一个自创的命令名称来替换原本的命令名称。可以用alias命令来创建一个属于自己的命令别名,格式为:“alias 别名=命令”,若要取消一个别名的话则是用unalias命令,格式为:“unalias 别名”。例如以前每次用rm命令删除文件的时候都要被要求再确认是否执行删除操作,其实这就是Linux系统为了防止用户误删除文件而特意设置的rm别名命令,咱们可以把它取消掉:

[root@linuxprobe ~]# ls

anaconda-ks.cfg Documents initial-setup-ks.cfg Pictures Templates

Desktop Downloads Music Public Videos

[root@linuxprobe ~]# rm anaconda-ks.cfg 

rm: remove regular file ‘anaconda-ks.cfg’? y

[root@linuxprobe~]# alias rm

alias rm='rm -i'

[root@linuxprobe ~]# unalias rm

[root@linuxprobe ~]# rm initial-setup-ks.cfg 

[root@linuxprobe ~]#

第3步骤阶段就是由Bash解释器来判断用户输入的是个内部命令还是个外部命令,内部命令是解释器内部的指令,会被直接的执行,而绝大部分的时候都会是外部命令,交由给第4步骤来继续处理,您可以使用“type 命令名称”来手工判断是内部命令还是外部命令,也是很有趣的。

第4步骤阶段是系统在多个路径中查找用户输入的命令文件,而定义这些路径的变量叫做PATH,可以简单把它理解成是“解释器的小助手”,作用是告诉Bash解释器要执行的命令可能存放到了那里,然后Bash解释器就会乖乖的在这些目录中逐个查找。PATH是由多个路径值组成的变量,每个路径值之间用冒号间隔,咱们对PATH变量内这些路径的增加和删除操作将会直接影响bash解释器搜索linux命令的位置。

[root@linuxprobe ~]# echo $PATH

/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin

[root@linuxprobe ~]# PATH=$PATH:/root/bin

/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:/root/bin

这里有比较经典的问题:“为什么不能在$PATH中添加进当前目录(.)那?”仔细想想再看答案!

答案:虽然把$PATH变量添加了当前目录(.)会在一些情况让用户免去输入命令所在路径的麻烦,但如果黑客在比较常用的公共目录/tmp中存放了一个名为"ls"或"cd"的同名木马文件,那么用户就极有可能错误的执行了。

所以作为一名态度谨慎、有经验的运维人员在接手了一台Linux系统后一定会在执行命令前先检查下PATH变量中是否有可疑的目录,另外读者们从PATH变量的例子中是不是也感觉到环境变量特别实用呢,您可以使用env命令来查看到linux系统中所有的环境变量,而刘遄老师为您精挑细选出了最重要的10个环境变量:

变量名称 - 作用

HOME - 用户的主目录(即家目录)。

SHELL - 用户在使用的SHELL解释器名称。

HISTSIZE - 历史命令记录条数。

HISTFILESIZE - 历史命令记录条数。

MAIL - 邮件信箱文件保存路径。

LANG - 系统语言、语系名称。

RANDOM - 生成一个随机数字。

PS1 - bash解释器的提示符。

PATH - 定义解释器搜索用户执行命令的路径。

EDITOR - 用户默认的文本编辑器。

Linux系统为了能够为每个用户提供独立的、合适的工作运行环境,因此在不同的用户身份下提取一个相同的变量也可能会获得不同的值,例如查看下HOME变量在不同用户身份下的值都有那些吧(su是用于切换用户身份的命令,在第5章有讲。):

[root@linuxprobe ~]# echo $HOME

/root

[root@linuxprobe ~]# su - linuxprobe

Last login: Fri Feb 27 19:49:57 CST 2017 on pts/0

[linuxprobe@linuxprobe ~]$ echo $HOME

/home/linuxprobe

其实变量是由固定的变量名与用户或系统设置的变量值两部分组成的,如果工作需要完全可以自己手工创建的,例如设置一个名称为WORKDIR的变量,方便用户更轻松的进入一个很深层的目录:

[root@linuxprobe ~]# mkdir /home/workdir

[root@linuxprobe ~]# WORKDIR=/home/workdir

[root@linuxprobe ~]# cd $WORKDIR 

[root@linuxprobe workdir]# pwd

/home/workdir

但是这样的变量不具有全局性,作用范围也是有限的,默认不能够被其他用户使用的,如果工作需要的话咱们可以使用export命令将其提升为全局变量,这样其他的用户也就可以使用到这个变量了:

[root@linuxprobe workdir]# su linuxprobe

Last login: Fri Mar 20 20:52:10 CST 2017 on pts/0

[linuxprobe@linuxprobe ~]$ cd $WORKDIR

[linuxprobe@linuxprobe ~]$ echo $WORKDIR

[linuxprobe@linuxprobe ~]$ exit

[root@linuxprobe ~]# export WORKDIR

[root@linuxprobe workdir]# su linuxprobe

Last login: Fri Mar 20 21:52:10 CST 2017 on pts/0

[linuxprobe@linuxprobe ~]$ cd $WORKDIR

[linuxprobe@linuxprobe workdir]$pwd

/home/workdir

 

本章节的复习作业

1:把ls命令正常输出信息追加写入到error.txt文件中的命令是?

答案:ls >> error.txt

2:现在您能简述下管道符的作用吗?

答案:把左面(前面)命令的输出值作为右面(后面)命令的输入值以便进一步处理信息。

3:请问bash解释器通配符中星号(*)代表几个字符?

答案:零个或多个。

4:PATH变量的作用是?

答案:设定解释器搜索执行命令的路径。

5:请问什么命令能够把名称为LINUX的一般变量转换成全局变量?

答案:执行export LINUX命令即可。

6:若想要把整个字符串中所有的字符都转义成纯文字数据,不允许变量生效的话怎么实现?

答案:需要使用转义符中的单引号,把整个字符串信息括起来。

精选文章
热门文章