第3章 管道符、重定向与环境变量
章节概述:
Don't be so excited!虽然此刻您已经学完了上百个常用Linux命令,但如前面所说:“光用命令本身并不能做好工作”。
下个章节将学习Shell脚本的使用方法,所以本章节要有些承上启下的作用,理论知识点会比较多,但都很实用。
当读者学习完管道命令符、输入输出重定向、通配符以及环境变量后便可以将命令组合的更加恰当、高效率。
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的文件则是不存在的,输出文件的不存在报错提示信息也是该命令的错误输出信息,那么我们要想将原本输出到屏幕上的数据转向写入到文件当中,就要区别对待这两种输出信息才行。
对于输入重定向有这些情况:
符号 | 作用 |
命令 < 文件 | 将文件作为命令的标准输入 |
命令 << 分界符 | 从标准输入中读入,直到遇见“分界符”才停止 |
命令 < 文件1 > 文件2 | 将文件1作为命令的标准输入并将标准输出到文件2 |
对于输出重定向符有这些情况:
符号 | 作用 |
命令 > 文件 | 将标准输出重定向到一个文件中(清空原有文件的数据) |
命令 2> 文件 | 将错误输出重定向到一个文件中(清空原有文件的数据) |
命令 >> 文件 | 将标准输出重定向到一个文件中(追加到原有内容的后面) |
命令 2>> 文件 | 将错误准输出重定向到一个文件中(追加到原有内容的后面) |
命令 >> 文件 2>&1 或 命令 &> 文件 | 将标准输出与错误输出共同写入到文件中(追加到原有内容的后面) |
先来小试牛刀下吧,通过标准输出将“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. ………………省略部分输出信息………………
有没有感觉到特别方便?那么接下来动手试试输出重定向技术中清空写入与追加写入的两种不同模式带来的变化吧~我们先通过清空模式来向文件写入一行数据(该文件中包含上一个实验的信息),然后再通过追加模式向文件再写入一次数据,最终我们看到的文件内容会是这个样子的:
[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 2014 anacrontab
-rw-r--r--. 1 root root 55 Jan 29 2014 asound.conf
-rw-r--r--. 1 root root 1 Jan 29 2014 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 2015 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梦吧,机器猫经常为了大雄而从口袋中掏出一件件宝贝,好多次就用到了任意门这个道具,有一集大雄为了邀请朋友们来家玩,于是用任意门穿越到了火星上面,还把家里的水管放到了火星上,那么管道符就好像是数据用于穿越的任意门,帮助我们提高工作效率,完成之前不敢想象的事情。
出现问题?大胆提问!
因读者们硬件不同或操作错误都可能导致实验配置出错,请耐心再仔细看看操作步骤吧,不要气馁~
Linux技术交流请加A群:560843(满),B群:340829(推荐),C群:463590(推荐),点此查看全国群。
*本群特色:通过口令验证确保每一个群员都是《Linux就该这么学》的读者,答疑更有针对性,不定期免费领取定制礼品。
3.3 命令行的通配符
我在讲课时碰到有需要让学生记住的知识点时,普遍都会为了能让同学们打起精神来突然提高嗓门,所以这句话普遍都深刻到我的学生脑子中——“Linux系统中一切都是文件,而配置一个服务就是在修改其配置文件的参数。”,而且我们在日常的工作中文件名一般就是最常见到的参数对象了吧,如果我们此时需要遍历查找出以某个关键词开头的所有文件该如何操作呢?又例如要想批量查看所有硬盘文件的相关权限属性,笨笨的命令会是这样的:
[root@localhost ~]# ls -l /dev/sda brw-rw----. 1 root disk 8, 0 May 4 15:55 /dev/sda [root@localhost ~]# ls -l /dev/sda1 brw-rw----. 1 root disk 8, 1 May 4 15:55 /dev/sda1 [root@localhost ~]# ls -l /dev/sda2 brw-rw----. 1 root disk 8, 2 May 4 15:55 /dev/sda2 [root@localhost ~]# ls -l /dev/sda3 ls: cannot access /dev/sda3: No such file or directory
幸亏我的硬盘文件和分区只有3个,要是有几百个的话,估计一天的工作都要忙活这个事了。虽然我们在未来的第六章才会讲Linux系统的存储结构和FHS协议规范,但其实我们此时已经能看出一些简单规律了,比如这些文件共性都是以sda开头并且存放到了/dev目录中,那即便不知道分区编号和具体分区的个数也一样可以用通配符来搞定。通配符故名意思就是通用的匹配信息的符号,比如星号(*)就是代表匹配零个或多个字符,问号(?)是代表匹配单个字符,括号内加上数字([0-9])代表匹配单个阿拉伯数字的字符而括号内加上字母([abc])则是代表匹配单个指定的英文字母。俗话讲百闻不如一见,看书不如做实验,例如匹配下所有在/dev目录中且以sda开头的文件吧:
[root@localhost ~]# 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@localhost ~]# 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@localhost ~]# 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@localhost ~]# ls -l /dev/sda[135] brw-rw----. 1 root disk 8, 1 May 4 15:55 /dev/sda1
3.4 常用的转义字符
Shell解释器为了能够更好的理解您想表达的意思,还提供了特别丰富的转义符号来帮助程序员处理输入的特殊数据,为遵循您手中这本《Linux就该这么学》的创作主旨,我愣是结合了多年工作学习经验,用了两周时间为读者从数十个转义符中提炼出了四个最常用符号!这也让我深刻的反省了很长时间,原本认为书籍能写的越厚越肯定是大神级人物的这种多年错误观念,希望读者您在读完本书后也能够体会到刘遄的一份用心。常见的转义字符包括有:反斜杠(\)的作用就是转义后面的一个字符变为单纯的字符串,单引号('')则是转移其中所有的字符为单纯的字符串,而双引号("")是保留其中的变量属性不转义,反引号(``)则是将其中的命令执行后返回一个结果。
例如我们先定义一个赋值为5且名称为PRICE的变量,然后通过双引号括起来输出字符串与变量结合的结果:
[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
好的,我们看似转义符学习的非常顺利,但最后一个您可能看到结果时会觉得很无用,暂且先不用管具体的使用场景,就当作是提前为第四章的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 2014 x86_64 x86_64 x86_64 GNU/Linux
3.5 重要的环境变量
变量是计算机系统中用于保存可变值的数据类型,在Linux系统中一般变量名称都是大写的,这仅算是一种约定俗成的规范,我们平时可以直接通过变量名称来提取到对应的变量值。Linux系统中的环境变量是用来指定系统运行环境的一些参数,比如每个用户不同的家目录、邮件保存存放位置等等。细心的读者一定了发现上一个小节与本小节都分别加了形容词——常见的、重要的,原因其实不言而喻,因为为了通过环境变量帮助Linux系统构建起能够为用户提供服务的工作运行环境,是需要数百个变量协同完成的,您当然没有必要去把每一个变量都看一遍,而应该在最宝贵的书籍中为读者精讲最重要的内容。比如我们前面小节中提到的一个概念——即Linux系统中一切都是文件,因此Linux命令肯定也不例外,那当用户执行了一条命令之后到底发生了什么事情呢?简单来说就是四个步骤:
第一步骤阶段是判断用户是否以绝对路径或相对路径的方式输入命令(如/bin/ls),如果是的话则直接执行。
第二步骤阶段是检查用户输入的命令是否为“别名命令”,即用一个自创的命令名称来替换原本的命令名称。我们可以用alias命令来创建自己的命令别名,格式为:“alias 别名=命令”,若要取消一个别名的话则是用unalias命令,格式为:“unalias 别名”。例如我们以前用rm命令删除文件的时候都要被要求再确认是否执行删除操作,其实这就是Linux系统为了防止用户误删除文件而特意设置的rm别名命令,我们可以将它取消掉:
[root@localhost ~]# ls anaconda-ks.cfg Documents initial-setup-ks.cfg Pictures Templates Desktop Downloads Music Public Videos [root@localhost ~]# rm anaconda-ks.cfg rm: remove regular file ‘anaconda-ks.cfg’? y [root@localhost ~]# alias rm alias rm='rm -i' [root@localhost ~]# unalias rm [root@localhost ~]# rm initial-setup-ks.cfg [root@localhost ~]#
第三步骤阶段就是由SHELL解释器(Bash)来判断用户输入的是内部命令还是外部命令,内部命令是解释器内部的指令,会被直接的执行,而绝大部分的时候都会是外部命令,交由给第四步骤来继续处理,当然您还可以使用“type 命令名称”来手工判断是内部命令还是外部命令,也是很有趣的。
第四步骤阶段是系统在多个路径中查找用户输入的命令文件,而定义这些路径的变量叫做PATH,可以简单把它理解成是“解释器的小助手”,作用是负责告诉解释器用户要执行的命令可能存放到了那里,然后bash就会乖乖的在这些目录中逐个查找。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 | 历史命令记录条数。 |
邮件信箱文件保存路径。 | |
LANG | 系统语言、语系名称。 |
RANDOM | 生成一个随机数字。 |
PS1 | bash解释器的提示符。 |
PATH | 定义解释器搜索用户执行命令的路径。 |
EDITOR | 用户默认的文本编辑器。 |
Linux系统为了能够为每个用户提供独立的、合适的工作运行环境,因此在不同的用户身份下提取一个相同的变量也可能会获得不同的值,例如我们查看下HOME变量在不同用户身份下的值都有那些:
[root@linuxprobe ~]# echo $HOME /root [root@linuxprobe ~]# su - linuxprobe Last login: Fri Feb 27 19:49:57 CST 2015 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 2015 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 2015 on pts/0
[linuxprobe@linuxprobe ~]$ cd $WORKDIR
[linuxprobe@linuxprobe workdir]$pwd
/home/workdir
出现问题?大胆提问!
因读者们硬件不同或操作错误都可能导致实验配置出错,请耐心再仔细看看操作步骤吧,不要气馁~
Linux技术交流请加A群:560843(满),B群:340829(推荐),C群:463590(推荐),点此查看全国群。
*本群特色:通过口令验证确保每一个群员都是《Linux就该这么学》的读者,答疑更有针对性,不定期免费领取定制礼品。
本章节的复习作业(答案就在问题的下一行哦,用鼠标选中即可看到的~)
1:现在您能简述下管道符的作用吗?
答案:将左面命令的输出值作为右面命令的输入值。
2:将命令的错误输出信息写入到error.txt文件中的命令是?
答案:命令 2>error.txt
3:bash的通配符中*代表几个字符?
答案:零个或多个。
4:PATH变量的作用是?
答案:设定执行命令的搜索路径。
5:什么命令能将变量转换成全局变量?
答案:export 变量名。