linux 入门系列 7-- 管道符、重定向、环境变量
前面文章我们学习了linux基础命令,如果将不同命令组合使用则可以成倍提高工作效率。本文将学习重定向、管道符、通配符、转义符、以及重要的环境变量相关知识,为后面的shell编程打下基础。
一、IO重定向
前文我们已经讲解了近60个linux常用命令,前文讲解的查看当前目录下有哪些文件的ls命令
~~~
[root@heimatengyun test]# ls
test1.txt test2.txt
~~~
执行命令后默认结果是输出到电脑屏幕(显示器)上的,但是如果我们想让命令执行结果保存到文件中,方便以后需要的时候可以随时查阅,我们该怎么做呢?这就要用到重定向的知识。
1.1 重定向概述
Linux shell重定向是指修改系统命令的默认执行方式,我们可以理解为“改变输入和输出的方向”,分为输入重定向和输出重定向。
既然重定向是改变默认的输入输出方向,那默认的输入输出方向又是什么呢?
相对程序而已,从键盘读取用户输入数据供程序使用,也就是数据流从键盘到程序,这就是标准的*输入;程序运算产生的结果数据直接呈现在显示器上,也就是数据流从程序到显示器,这就是标准的输出*。默认的标准输入、输出如下图:
将默认的从键盘读取数据改为从文件读取数据,也就是数据流从文件到程序,就是*输入重定向;程序运算产生的结果数据不显示在显示器上而是改为输入到文件,也就是数据流从程序到文件,就是输出重定向*。
计算机的硬件设备有很多,常见的输入设备有键盘、鼠标、麦克风、手写板等,输出设备有显示器、投影仪、打印机等。 不过,在Linux中,标准输入设备一般指键盘,标准输出设备一般指显示器。
前文提到过,*Linux中一切皆文件,包括键盘、显示器等输入输出设备在内的所有计算机硬件都是文件。为了表示和区分已经打开的文件,Linux会为每个文件分配一个ID,这个ID是一个整数,被称为文件描述符*(File Descriptor)。
与输入输出有关的文件描述符如下表:
| 文件描述符 | 文件名 | 类型 | 硬件 |
| :--------- | :----- | :--------------- | :----- |
| 0 | stdin | 标准输入文件 | 硬盘 |
| 1 | stdout | 标准输出文件 | 显示器 |
| 2 | stderr | 标准错误输出文件 | 显示器 |
Linux程序在执行任何形式的IO操作时,都是在读取或写入一个文件描述符。一个文件描述符只是一个和打开的文件相关联的整数,它被扣可能是一个硬盘上的普通文件、FIFO、管道、终端、键盘、显示器,甚至是一个网络连接。stdin、stdout、stderr默认都是打开的,在重定向的过程中,0、1、2这三个文件描述符可以直接使用。
1.2 重定向分类
重定向分为输入和输出重定向。简言之,输入重定向就是把文件导入到命令,输出重定向则是把原本要输出到屏幕的信息写入到指定文件中。平时工作中,相对于输入重定向,输出重定向使用频率更高,因此又将输出重定向细分为标准输出重定向和错误输出重定向,输出重定向又分为:清空写入和追加两种模式。
关于标准输出和错误输出请看下面示例:
~~~
[root@heimatengyun ~]# ls test/
test1.txt test2.txt
[root@heimatengyun ~]# ls xxx
ls: cannot access xxx: No such file or directory
~~~
用ls命令查看制定目录下的文件信息,如果文件夹存在且文件夹下有内容将输出文件所有者、所属组、文件大小等信息,也就是ls命令的*标准输出信息。但是如果查看一个不存在的文件夹,则提示文件不存在的报错信息,也就是ls命令的错误输出信息*。如果要把上边原本输出到屏幕的信息直接写入到文件中而不是显示到屏幕,就要区别对待这两种输出信息。
1.2.1 输入重定向
输入重定向相关的符号和作用如下表
| 符号 | 作用 |
| :------------------- | :------------------------------------------- |
| 命令 < 文件 | 将文件作为命令的标准输入 |
| 命令 << 分界符 | 从标准输入中读入,直到遇到分解符才停止 |
| 命令 < 文件1 > 文件2 | 将文件1作为命令的标准输入并将标准输出到文件2 |
输入重定向相对来说用的很少,输入重定向的作用是将文件直接导入到命令中。/etc/passwd文件存储了系统用户信息,一行记录一个用户。以下示例演示通过输入重定向将此文件导入到wc命令,统计用户个数。
~~~
[root@heimatengyun test]# wc -l < /etc/passwd
39
~~~
1.2.2 输出重定向
输出重定向用到的符号和作用如下表
| 符号 | 作用 |
| :---------------- | :--------------------------------------------------------- |
| 命令 1> 文件 | 将标准输出重定向到文件中(清空原有文件数据),1可以省略 |
| 命令 2> 文件 | 将错误输出重定向到文件中(清空原有文件数据) |
| 命令 1>> 文件 | 将标准输出重定向到文件中(追加到原有内容后面),1可以省略 |
| 命令 2>> 文件 | 将错误输出重定向到文件中(追加到原有内容后面) |
| 命令 &>> 文件 | 将标准输出和错误输出共同写入到文件中(追加到原有内容后面) |
| 命令 >> 文件 2>&1 | 同上条命令:命令 &>> 文件 |
对于重定向中的标准输出模式,一般省略文件描述符1不写,而错误输出模式的文件描述符2是必须要写的。
1.2.3 输出重定向案例
通过man命令查看ls命令的使用方法,并将输出信息重定向到ls.txt文件中,然后就可以使用cat命令查看ls.txt文件的信息。
~~~
[root@heimatengyun test]# ls
test1.txt test2.txt
[root@heimatengyun test]# man ls > ls.txt
[root@heimatengyun test]# ls
ls.txt test1.txt test2.txt
[root@heimatengyun test]# cat ls.txt
LS(1) User Commands LS(1)
NAME
ls - list directory contents
SYNOPSIS
ls [OPTION]... [FILE]...
DESCRIPTION
...省略部分内容
~~~
接下来我们演示清空写入和追加写入的区别,先通过覆盖写入模式向ls.txt文件(原本有ls的帮助信息内容)写入一行数据,查看内容变化,然后再通过追加写入模式向文件写入一次数据,再查看文件内容的变化
~~~
[root@heimatengyun test]# echo "wellcome" > ls.txt
[root@heimatengyun test]# cat ls.txt
wellcome
[root@heimatengyun test]# echo "write message again" >> ls.txt
[root@heimatengyun test]# cat ls.txt
wellcome
write message again
~~~
可以看到覆盖模式将清空文件原有内容,追加模式则在原有内容后面添加数据。
1.2.4 标准输出和错误输出区别
虽然都是重定向技术,不同命令的标准输出和错误输出还是有区别的。如果一个命令执行成功,通过标准输出到文件是没有问题的,但是如果要错误输出重定向到文件是不会成功的,依旧会显示信息到屏幕。反之,如果一个命令执行失败,通过错误输出到文件是没有问题的,但是如果要输出到标准输出是不会成功的,依旧会显示到屏幕。
通过ls命令查看一个已经存在的文件,并将信息重定向到ls.txt文件,查看该文件可以看到成功存入信息。将其错误输出重定向到ls-err.txt文件,由于查看的该文件存在,没有错误信息所以看到查询成功的文件信息依然显示在了屏幕上,而错误重定向的文件里没有内容。
~~~
[root@heimatengyun test]# ls
test1.txt test2.txt
[root@heimatengyun test]# ls -l test1.txt
-rw-r--r--. 1 root root 0 Nov 30 15:34 test1.txt
[root@heimatengyun test]# ls -l test1.txt > ls.txt
[root@heimatengyun test]# ls
ls.txt test1.txt test2.txt
[root@heimatengyun test]# cat ls.txt
-rw-r--r--. 1 root root 0 Nov 30 15:34 test1.txt
[root@heimatengyun test]# ls -l test1.txt 2> ls-err.txt
-rw-r--r--. 1 root root 0 Nov 30 15:34 test1.txt
[root@heimatengyun test]# ls
ls-err.txt ls.txt test1.txt test2.txt
[root@heimatengyun test]# cat ls-err.txt
~~~
二、管道符
通过管道符可以把很多命令组合起来,提高工作效率。简言之管道符的作用就是:把前一个命令原本要输出到屏幕的标准正常数据当作后一个命令的标准输入。
管道符用|表示,使用格式为:*命令A|命令B|命令C...*
案例1:统计被禁止登陆系统的用户数
~~~
[root@heimatengyun test]# grep "/sbin/nologin" /etc/passwd |wc -l
34
~~~
通过“linux入门系列5--新手必会的linux命令”介绍的grep命令匹配/etc/passwd文件中的关键字“/sbin/nologin”查找出被限制登陆系统的用户,并将匹配结果输入到wc命令,统计匹配到的行数,即为被限制登陆系统的用户数。
案例2:将文件内容中的小写字母替换为大写字母输出
~~~
[root@heimatengyun test]# cat test1.txt
wellcome
[root@heimatengyun test]# cat test1.txt |tr [a-z] [A-Z]
WELLCOME
[root@heimatengyun test]# cat test1.txt
wellcome
~~~
通过cat命令读取test1.txt文件内容并导入到tr命令,通过tr命令将内容中的小写字母替换为大写字母。可以看到只是对读取后的内容进行替换,对原文件没有影响。
tr命令作用是替换文本文件中的字符,格式为:*tr [原始字符] [目标字符]*
很多时候想要快速地替换文本中的一些词汇,如果手工替换,难免工作量巨大,尤其是需要处理大批量内容的时候。这时tr命令就可以派上用场,通过管道符将文本内容传递给它进行替换操作即可。
ps:前文讲了近60个Linux命令,命令太多不可能一一涵盖,其余的命令将根据场景需求逐步以案例的形式分散到各文中进行演示。
三、通配符
通配符的概念在很多语言中都存在,比如java、c#等,其作用就是模糊匹配。
假设你在电脑上存放了很多小电影,某一天突然想看某位老师的电影作品,但是由于文件太多以至于记不清楚电影文件的名称了,只是依稀记得文件名包含了几个关键字,这时候你怎么快速找到对应的文件呢?
通配符就是面对这种场景而生,熟练使用通配符,再多电影都不迷路。通配符顾名思义就是通用的匹配信息的符号,主要包含以下几个:
| 符号 | 意义 |
| :---- | :---------------------------------------- |
| * | 匹配0个和多个字符 |
| ? | 匹配单个字符 |
| [0-9] | 匹配0~9之间的单个数字字符 |
| [123] | 匹配1、2、3这三个指定数字中的任意一个数字 |
| [abc] | 匹配a、b、c三个字符中的任意一个字符 |
案例1:匹配文件名以test开头的所有文件
~~~
[root@heimatengyun test]# ls
test1.txt test2.txt
[root@heimatengyun test]# ls -l test*
-rw-r--r--. 1 root root 9 Nov 30 20:43 test1.txt
-rw-r--r--. 1 root root 0 Nov 30 15:34 test2.txt
~~~
案例2:匹配文件名最后一位为1或3的所有文件
~~~
[root@heimatengyun test]# ls
test1.txt test2.txt
[root@heimatengyun test]# ls -l test[13].txt
-rw-r--r--. 1 root root 9 Nov 30 20:43 test1.txt
~~~
四、转义符
“linux入门系列5--新手必会的linux命令”提到,人和Linux内核之间的交互是通过在shell终端中执行相关命令来实现的,为了能更好地理解用户的表达,除了通配符、管道符,shell解释器还提供了特别丰富的转义字符来处理用户输入的特殊数据。
本文只抽取几个常用的通配符进行讲解,转义符及对应的功能如下:
| 转义符 | 作用 |
| ------ | :----------------------------------------- |
| \ | 反斜杠,使后边的一个变量变为单纯的字符串 |
| '' | 单引号,转义其中所有的变量为单纯的字符串 |
| "" | 双引号,保留其中的变量属性,不进行转义处理 |
| `` | 反引号,把其中的命令执行后返回结果 |
案例1:输出美元$表示的价格
~~~
[root@heimatengyun test]# PRICE=99
[root@heimatengyun test]# echo "the price is $PRICE"
the price is 99
[root@heimatengyun test]# echo "the price is $$PRICE"
the price is 12395PRICE
~~~
定义PRICE变量保存价格,然后通过echo命令输出,发现输出的不是预期结果。原因是Linux中$表示变量,$$则有特殊的作用,表示当前程序的进程ID号。这时就需要反斜杠来进行转义,去除其特殊功能,将这个提取符转义为单纯的文本。
~~~
[root@heimatengyun test]# echo "the price is \$$PRICE"
the price is $99
~~~
案例2:将命令执行结果值赋值给变量并输出
~~~
[root@heimatengyun test]# uname -a
Linux heimatengyun 3.10.0-123.el7.x8664 #1 SMP Mon Jun 30 12:09:22 UTC 2014 x8664 x8664 x8664 GNU/Linux
[root@heimatengyun test]# MYSYS=uname -a
[root@heimatengyun test]# echo $MYSYS
Linux heimatengyun 3.10.0-123.el7.x8664 #1 SMP Mon Jun 30 12:09:22 UTC 2014 x8664 x8664 x8664 GNU/Linux
~~~
用uname命令查看当前操作系统信息,并赋值给变量MYSYS,然后输出变量值。更多变量相关知识将在下一篇文章中详细介绍,此处主要掌握反引号这个转义符。
五、环境变量
变量是计算机系统用于保存可变值的数据类型,*在Linux系统中,变量名称一般是大写的,这是一种约定俗成的规范*。直接通过变量名即可获得对应的变量值。
环境变量是一种特殊的变量,是操作系统要正常运行的前提,数百个环境变量协同工作才使得操作系统能正常为用户提供服务。然而我们没有必要去全部学习和掌握所有数百个环境变量,只需要学习一部分常用的即可。
5.1 查看环境变量之env命令
一般通过env命令查看环境变量名
~~~
[root@heimatengyun test]# env
XDGSESSIONID=2
HOSTNAME=heimatengyun
SELINUXROLEREQUESTED=
TERM=linux
SHELL=/bin/bash
...省略部分内容
~~~
用echo命令查询环境变量值
~~~
[root@heimatengyun test]# echo $SHELL
/bin/bash
~~~
*Linux作为一个多用户多任务操作系统,能够为每个用户提供独立的工作环境,因此,一个相同的变量会因为用户身份的不同而具有不同的值*。
案例:使用不同用户查看HOME环境变量的值
~~~
[root@heimatengyun ~]# echo $HOME
/root
[root@heimatengyun ~]# su - test
Last login: Sat Nov 30 22:39:50 CST 2019 on pts/0
[test@heimatengyun ~]$ echo $HOME
/home/test
[test@heimatengyun ~]$ exit
logout
[root@heimatengyun ~]#
~~~
案例中先使用root用户查看$HOME的值,然后切换到test用户再次查看$HOME,从试验结果上看相同环境变量值却是不一样的。
注意:关于用户切换命令su的用法,su test和su - test是有非常大区别的,如果不加-表示只是切换用户不切换shell环境,如果加上-则表示连同shell环境一起替换,此处无论是否切换shell环境,两个不同用户的$HOME值都不一样。
5.2 设置环境变量之export命令
变量是由固定的变量名与用户或系统设置的变量值两部分注册,因此我们完全可以根据工作需要自行创建变量。
以下案例演示创建一个名称为$MYDIR,值为/etc/profile.d/ 目录的自定义变量,这样我们只需要通过该变量,就可以很方便的进入到值对应的目录。
~~~
[root@heimatengyun test]# MYDIR=/etc/profile.d/
[root@heimatengyun test]# echo $MYDIR
/etc/profile.d/
[root@heimatengyun test]# pwd
/root/test
[root@heimatengyun test]# cd $MYDIR
[root@heimatengyun profile.d]# pwd
/etc/profile.d
~~~
此时创建的变量$MYDIR不具有全局性,作用范围有限,默认情况下不能被其他用户使用。
~~~
[root@heimatengyun ~]# su test
[test@heimatengyun root]$ echo $MYDIR
[test@heimatengyun root]$ exit
exit
~~~
可以看到切换到test用户后,该变量没有值,并且通过env命令查看也未查到该变量。
如果要想让其他用户也可以使用该变量,则需要用export命令,将其提升为全局变量。注意export命令后的变量名不加$。
~~~
[root@heimatengyun ~]# export MYDIR
[root@heimatengyun ~]# env
XDGSESSIONID=2
HOSTNAME=heimatengyun
SELINUXUSECURRENT_RANGE=
MYDIR=/etc/profile.d/
...省略部分内容
~~~
通过env命令也可以查到该变量,此时我们在切换到test用户查看是否可以使用
~~~
[root@heimatengyun ~]# su test
[test@heimatengyun root]$ echo $MYDIR
/etc/profile.d/
[test@heimatengyun root]$ exit
exit
~~~
注意:再次强调一下,su test和su - test是有非常大区别的,如果不加-表示只是切换用户不切换shell环境。本例只是切换用户到test并没切换环境所以可以使用$MYDIR,如果使用su - test 把shell环境也替换的话,将无法使用自定义的$MYDIR环境变量。
5.3 常用的环境变量
下表列举几个重要常用的环境变量
| 变量名称 | 作用 |
| :----------- | :------------------------------- |
| HOME | 用户家目录 |
| SHELL | 用户在使用的shell解释器名称 |
| HISTSIZE | 输出的历史命令记录条数 |
| HISTFILESIZE | 保存的历史命令记录条数 |
| LANG | 系统语言、语系名称 |
| PATH | 定义解释器搜索用户执行命令的路径 |
5.4 命令执行流程
Linux系统中一切皆文件,Linux命令也不例外。当用户执行一条命令之后,Linux系统到底发生了什么事情呢?
简单来说,命令在Linux中的执行分为以下4个步骤
(1)判断用户是否以绝对路径或相对路径的方式输入命令,如果是则直接执行,不是则进行第二步
(2)检查用户输入的命令是否有别名命令,如果有别名找到原命令,如果无则进行第三步
(3)bash解释器判断用户输入的是内部命令还是外部命令,如是内部命令则直接执行,外部命令则进行第四步
(4)系统在PATH环境变量中查找用户输入的命令,找到文件后执行命令。
*简单理解就是用户通过shell输入命令,shell解释器查找对应的命令文件并执行命令*。
注意:思考一下一个经典的问题,能否将当前目录(.)添加到环境变量PATH中呢?
>
尽管可以将当前目录(.)添加到PATH变量中,使得在某些情况下可以让用户免去输入命令路径的麻烦,但是,这样存在很大的安全风险。假如黑客在常用的公用目录/tmp下存放一个与ls或cd等命令同名的病毒文件,而用户恰巧又在公共目录中执行了这些命令,那就很可能中招了。
了解linux命令执行流程后,当接手一台Linux系统后,在执行命令前先检查PATH变量中是否有可以的目录,这是一个很好的习惯。
至此,已经学习了大部分Linux命令,知识积累的差不多了,下一篇文章我们将综合前面学到的知识,正式进入shell编程。
自学帮
版权声明: 本文为 InfoQ 作者【黑马腾云】的原创文章。
原文链接:【http://xie.infoq.cn/article/929a439b3b748da58207b7e8d】。未经作者许可,禁止转载。
评论