把多个 Linux 命令适当地组合到一起,使其协同工作,以便更加高效地处理数据。要做到这一点,就必须搞明白命令的输入重定向和输出重定向的原理。
输出重定向则是指把原本要输出到屏幕的数据信息写入到指定文件中。在日常的学习和工作中,相较于输入重定向,我们使用输出重定向的频率更高,所以又将输出重定向分为了标准输出重定向和错误输出重定向两种不同的技术,以及覆盖写入与追加写入两种模式。
标准输入重定向(STDIN,文件描述为 0 ):默认从键盘输入,也可以从其他文件或命令中输入。
标准输出重定向(STDOUT,文件描述为 1 ):默认输出到屏幕。
分别查看两个文件的属性信息,我们先创建出第一个文件,而第二个文件是不存在的。所以,虽然针对这两个文件的操作都分别会在屏幕上输出一些信息,但这两个操作的差异其实很大:
[root@servera ~]# touch 1
[root@servera ~]# ls -l 1
-rw-r--r--. 1 root root 0 Sep 22 09:42 1
[root@servera ~]# ls -l xxx
ls: cannot access 'xxx': No such file or directory
在上述命令中,名为 1 的文件是真实存在的,输出信息是该文件的一些相关权限、所有者、所属组、文件大小及修改时间等信息,这也是该命令的标准输出信息。而名为 xxx 的第二个文件是不存在的,因此在执行完 ls 命令之后显示的报错提示信息也是该命令的错误输出信息。那么,要想把原本输出到屏幕上的数据转而写入到文件当中,就要区别对待这两种输出信息。
符合 | 作用 |
---|---|
命令 < | 将文件作为命令的标准输入 |
命令 << 分界符 | 从标准输入中读入,直到遇见分解符才停止 |
命令 < 文件1 > 文件2 | 将文件1作为命令的标准输入 并将标准输出到文件2 |
输入重定向相对来说比较冷门,输入重定向的作用是把文件直接导入到命令中。
使用输入重定向把 readme.txt 文件导入给 wc -l 命令,统计一下文件中的内容行数:
[root@servera ~]# wc -l < readme.txt
3
[root@servera ~]# wc -l readme.txt
3 readme.txt
这次的输出结果与第 2 章讲的时候有所不同:没有了文件名称。
这是因为此前使用的“wc -l /etc/passwd”是一种非常标准的“命令+参数+对象”的执行格式。
而这次的“wc -l < readme.txt”则是将 readme.txt 文件中的内容通过操作符导入到命令中,没有被当作命令对象进行执行,因此 wc 命令只能读到信息流数据,而没有文件名称的信息。
符合 | 作用 |
---|---|
命令 > 文件 | 将标准输出重定向一个文件中(清空原有文件的数据) |
命令 2> 文件 | 将错误输出重定向到一个文件中(清空原有文件的数据) |
命令 >> 文件 | 将标准输出重定向一个文件中(追到到原有内容后面) |
命令 2>> 文件 | 将错误输出到一个文件中(追加原有内容后面) |
命令 >> 文件 2>&1 或 命令 & >> 文件 | 将标准输出与错误输出共同写入到文件中(追加到原有内容后边) |
通过标准输出重定向将 cat /etc/passwd 命令原本要输出到屏幕的信息写入到文件 readme.txt 中,然后显示 readme.txt 文件中的内容。具体命令如下:
[root@servera ~]# cat /etc/passwd > readme.txt
[root@servera ~]# cat readme.txt
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
......省略
\> 覆盖,首先通过覆盖写入模式向 readme.txt 文件写入多行数据(该文件中已包含上一个实验的 passwd 信息)。需要注意的是,在通过覆盖写入模式向文件中写入数据时,每一次都会覆盖掉上一次写入的内容,所以最终文件中只有最后一次的写入结果:
[root@servera ~]# echo 'Welcaome to www.zxbke.cn' > readme.txt
[root@servera ~]# echo 'Welcaome to www.zxbke.cn' > readme.txt
[root@servera ~]# cat readme.txt
Welcaome to www.zxbke.cn
\>> 追加,不会抹掉之前的原有内容,在原有内容后边继续追加
[root@servera ~]# echo 'Hello ya' >> readme.txt
[root@servera ~]# echo 'Hello ya' >> readme.txt
[root@servera ~]# cat readme.txt
Welcaome to www.zxbke.cn
Hello ya
Hello ya
readme.txt 文件是真实存在的,因此使用标准输出即可将原本要输出到屏幕的信息写入到文件中,而 read.txt 是不存在的,将错误的输出重定向追加到了1.txt。
[root@servera ~]# ls -l readme.txt 2>> 1.txt
-rw-r--r--. 1 root root 43 Sep 22 09:56 readme.txt
[root@servera ~]# ls -l read.txt 2>> 1.txt
[root@servera ~]# cat 1.txt
ls: cannot access 'read.txt': No such file or directory
还有一种常见情况,就是我们想不区分标准输出和错误输出,只要命令有输出信息则全部追加写入到文件中。这就要用到 &>> 操作符了:
[root@servera ~]# ls -l readme.txt &>> 1.txt
[root@servera ~]# ls -l read.txt &>> 1.txt
[root@servera ~]# cat 1.txt
-rw-r--r--. 1 root root 43 Sep 22 09:56 readme.txt
ls: cannot access 'read.txt': No such file or directory
管道命令符的作用可以用一句话概括 : 把前一个命令原本要输出到屏幕的信息当做后一个命令的标准输入。
grep 搜索命令的输出值传递给 wc 统计命令,即把原本要输出到屏幕的用户信息列表再交给 wc 命令作进一步的加工。
[root@servera ~]# grep /sbin/nologin passwd.txt | wc -l
40
比如用翻页的形式查看/etc 目录中的文件列表及属性信息
[root@servera ~]# ls -l /etc/ | more
在修改用户密码时,通常都需要输入两次密码以进行确认,这在编写自动化脚本时将成为一个非常致命的缺陷。通过把管道符和 passwd 命令的--stdin 参数相结合,可以用一条命令来完成密码重置操作
[root@servera ~]# echo 'zx123456' | passwd --stdin root
Changing password for user root.
passwd: all authentication tokens updated successfully.
输入ps aux 命令后屏幕信息呼呼闪过,根本找不到有用的信息。现在也可以将ps、grep、管道符三者结合到一起使用了。下面搜索与 bash 有关的进程信息:
[root@servera ~]# ps aux | grep bash
root 928 0.0 0.0 25512 2516 ? S 09:41 0:00 /bin/bash /usr/sbin/ksmtuned
root 2676 0.0 0.1 26432 5008 pts/0 Ss 10:18 0:00 -bash
root 2747 0.0 0.0 12112 1064 pts/0 R+ 10:20 0:00 grep --color=auto bash
如果需要将管道符处理后的结果既输出到屏幕,又同时写入到文件中,则可以与 tee 命令结合使用
[root@servera ~]# ps aux | grep bash | tee read.txt
root 928 0.0 0.0 25512 2516 ? S 09:41 0:00 /bin/bash /usr/sbin/ksmtuned
root 2676 0.0 0.1 26564 5200 pts/0 Ss 10:18 0:00 -bash
root 2772 0.0 0.0 12112 1080 pts/0 R+ 10:21 0:00 grep --color=auto bash
root 2773 0.0 0.0 26564 1840 pts/0 R+ 10:21 0:00 -bash
[root@servera ~]# cat read.txt
root 928 0.0 0.0 25512 2516 ? S 09:41 0:00 /bin/bash /usr/sbin/ksmtuned
root 2676 0.0 0.1 26564 5200 pts/0 Ss 10:18 0:00 -bash
root 2772 0.0 0.0 12112 1080 pts/0 R+ 10:21 0:00 grep --color=auto bash
root 2773 0.0 0.0 26564 1840 pts/0 R+ 10:21 0:00 -bash
有时候也会遇到明明一个文件的名称就在嘴边但就是想不起来的情况。如果只记得一个文件的开头几个字母,想遍历查找出所有以这几个字母开头的文件,该怎么操作呢?又比如,假设我们想要批量查看所有硬盘文件的相关权限属性,该怎么操作呢?:
通配符 | 含义 |
---|---|
* | 任意字符 |
? | 单个任意字符 |
[a-z] | 单个小写字母 |
[A-Z] | 单个大写字母 |
[a-Z] | 单个字母 |
[0-9] | 单个数字 |
[[:alpha:]] | 任意字母 |
[[:upper:]] | 任意大写字母 |
[[:lower:]] | 任意小写字母 |
[[:digit:]] | 所有数字 |
[[:alnum:]] | 任意字母加数字 |
[[:punct:]] | 标点符号 |
下面我们就来匹配所有在/dev 目录中且以 sda开头的文件:
[root@servera ~]# ls -l /dev/nv*
crw-------. 1 root root 243, 0 Sep 22 09:41 /dev/nvme0
brw-rw----. 1 root disk 259, 0 Sep 22 09:41 /dev/nvme0n1
brw-rw----. 1 root disk 259, 1 Sep 22 09:41 /dev/nvme0n1p1
brw-rw----. 1 root disk 259, 2 Sep 22 09:41 /dev/nvme0n1p2
crw-------. 1 root root 10, 144 Sep 22 09:41 /dev/nvram
如果只想查看文件名以 sda 开头,但是后面还紧跟其他某一个字符的文件的相关信息,这时就需要用到问号来进行通配了:
[root@servera ~]# ls -l /dev/nvme?
crw-------. 1 root root 243, 0 Sep 22 09:41 /dev/nvme0
除了使用[0-9]来匹配 0~9 之间的单个数字,也可以用[135]这样的方式仅匹配这 3 个指定数字中的一个;若没有匹配到数字 1 或 2 或 3,则不会显示出来:
[root@servera ~]# ls -l /dev/nvme0n1p[1-9]
brw-rw----. 1 root disk 259, 1 Sep 22 09:41 /dev/nvme0n1p1
brw-rw----. 1 root disk 259, 2 Sep 22 09:41 /dev/nvme0n1p2
[root@servera ~]# ls -l /dev/nvme0n1p[135]
brw-rw----. 1 root disk 259, 1 Sep 22 09:41 /dev/nvme0n1p1
通配符不一定非要放到最后面,也可以放到前面。比如,可以使用下述命令来搜索/etc/目录中所有以.conf 结尾的配置文件有哪些:
[root@servera ~]# ls -l /etc/*.conf
-rw-r--r--. 1 root root 55 Feb 1 2019 /etc/asound.conf
-rw-r--r--. 1 root root 25696 Dec 12 2018 /etc/brltty.conf
-rw-r--r--. 1 root root 1083 Apr 4 2018 /etc/chrony.conf
-rw-r--r--. 1 root root 1174 Aug 12 2018 /etc/dleyna-server-service.conf
......省略
通配符不仅可用于搜索文件或代替被通配的字符,还可以与创建文件的命令相结合,一口气创建出好多个文件。不过在创建多个文件时,需要使用大括号,并且字段之间用逗号间隔:
[root@servera ~]# touch {1,2,3,4,5}.conf
使用通配符还可以输出一些指定的信息:
[root@servera ~]# echo file{1,2,3,4,5}
file1 file2 file3 file4 file5
4 个最常用的转义字符如下所示。
反斜杠( \ ) :使反斜杠后面的一个变量变为单纯的字符。
单引号( ‘ ’ ):转义其中所有的变量为单纯的字符串。
双引号( “ ” ):保留其中的变量属性,不进行转义处理。
反引号( `` ):把其中的命令执行后返回结果。
先定义一个名为 zxbke 的变量并赋值为 www.zxbke.cn,然后分别输出以单引号和双引号括起来的字符串与变量信息:
[root@servera ~]# zxbke='www.zxbke.cn'
[root@servera ~]# echo '张旭博客地址为 $zxbke' #变量为单纯的字符串。
张旭博客地址为 $zxbke
[root@servera ~]# echo "张旭博客地址为 $zxbke" #保留其中的变量属性,不进行转义处理
张旭博客地址为 www.zxbke.cn
希望能够输出“Price is 5”,即“价格是 5 美元”的字符串内容,但碰巧美元符号与变量提取符号合并后的
[root@servera ~]# PRICE=5
[root@servera ~]# echo "Price is $$PRICE" #$$作用是显示当前程序的进程 ID 号码
Price is 2676PRICE
[root@servera ~]# echo "Price is \$$PRICE" #让第一个“$”作为美元符号,使用反斜杠(\)来进行转义
Price is $5
只需要某个命令的输出值,可以像命令
这样,将命令用反引号括起来,达到预期的效果。例如,将反引号与 uname -a 命令结合,然后使用 echo 命令来查看本机的 Linux 版本和内核信息:
[root@servera ~]# echo uname -a
uname -a
[root@servera ~]# echo `uname -a` #把其中的命令执行后返回结果
什么时候使用双引号?简单小技巧,虽然可能不够严谨,但绝对简单:
如果参数中出现了空格,就加双引号;如果参数中没有空格,那就不用加双引号。
变量是计算机系统用于保存可变值的数据类型。在 Linux 系统中,变量名称一般都是大写的,命令则都是小写的,这是一种约定俗成的规范。Linux 系统中的环境变量是用来定义系统运行环境的一些参数,比如每个用户不同的家目录、邮件存放位置等。可以直接通过变量名称来提取到对应的变量值。
在 Linux 系统中一切都是文件,Linux 命令也不例外。那么,在用户执行了一条命令之后,Linux 系统中到底发生了什么事情呢?简单来说,命令在 Linux 中的执行分为 4 个步骤。
这里有比较经典的问题:“为什么不能将当前目录(.)添加到 PATH 中呢?”原因是,尽管可以将当前目录(.)添加到 PATH 变量中,从而在某些情况下可以让用户免去输入命令所在路径的麻烦。但是,如果黑客在比较常用的公共目录/tmp 中存放了一个与 ls 或 cd 命令同名的木马文件,而用户又恰巧在公共目录中执行了这些命令,那么就极有可能中招了。
我们可以使用 env 命令来查看 Linux 系统中所有的环境变量,比较重要的 10 个环境变量,如表所示。
变量名称 | 作用 |
---|---|
HOME | 用户的主目录(即家目录) |
SHELL | 用户在使用的Shell解释名称 |
HISTSIZE | 输出的历史命令记录条数 |
HISTFILESIZE | 保存的历史命令记录条数 |
邮件保存路径 | |
LANG | 系统语言、语系名称 |
RANDOM | 生成一个随机数字 |
PS1 | Bash解释器的提示符 |
PATH | 定义解释器搜索用户执行命令的路径 |
EDITOR | 用户默认的文本编辑器 |
使用下述命令来查看 HOME 变量在不同的用户身份下都有哪些值
[root@servera ~]# echo $HOME #输出root用户下的HOME变量值
/root
[root@servera ~]# su - zhangxu
[zhangxu@servera ~]$ echo $HOME #输出zhangxu用户下的HOME变量值
/home/zhangxu
设置一个名称为 WORKDIR 的变量,方便用户更轻松地进入一个层次较深的目录:
[root@servera ~]# mkdir /home/workdir
[root@servera ~]# WORKDIR=/home/workdir
[root@servera ~]# cd $WORKDIR
[root@servera workdir]# pwd
/home/workdir
但是,这样的变量不具有全局性,作用范围也有限,默认情况下不能被其他用户使用:
如果工作需要,可以使用 export 命令将其提升为全局变量,这样其他用户也就可以使用它了:
[root@servera ~]# WORKDIR=/home/workdir #创建变量 WORKDIR=/home/root
[root@servera ~]# export WORKDIR #设置为全局变量 所有用户都可以使用
[root@servera ~]# cd $WORKDIR
[root@servera workdir]# pwd
/home/workdir
[root@servera workdir]# su zhangxu #切换成zhangxu用户
[zhangxu@servera workdir]$ cd $WORKDIR
[zhangxu@servera workdir]$ pwd
/home/workdir
若删除或查看本地环境变量
[root@zxbke ~]# set | grep WORKDIR
WORKDIR=/home/workdir
[root@zxbke ~]# unset WORKDIR
[root@zxbke ~]# set | grep WORKDIR