3、进一步使用Shell
(1)重定向
Linux命令在执行时常常期望接收输入数据,命令执行后又期望将产生的数据结果输出。Linux的大部分命令都具有标准的输入/输出设备端口。下表中列出了标准设备。
标准设备 | ||||
名称 | 代号 | 代表意思 | 设备 | 说 明 |
STDIN | 0 | 标准输入 | 键盘 | 命令在执行时所要的输入数据通过它来取得 |
STDOUT | 1 | 标准输出 | 显示器 | 命令执行后的输出结果从该端口送出 |
STDERR | 2 | 标准错误 | 显示器 | 命令执行时的错误信息通过该端口送出 |
所谓重定向,就是不使用系统的标准输入端口、标准输出端口或标准错误端口,而进行重新的指定,所以重定向分为输出重定向、输入重定向和错误重定向。通常情况下重定向到一个文件。在Shell中,要实现重定向主要依靠重定向符实现,即Shell是检查命令行中有无重定向符来决定是否需要实施重定向。下表中列出了常用的重定向符。
重定向符 | |
重定向符 | 说 明 |
< | 实现输入重定向。输入重定向并不经常使用,因为大多数命令都以参数的形式在命令行上指定输入文件的文件名。尽管如此,当使用一个不接受文件名为输入参数的命令,而需要的输入又是在一个已存在的文件里时,就能用输入重定向解决问题 |
<<!......! | 实现输入重定向的特例,即Here文件。两个!可以换成任意字符串,只要一致即可 |
>或>> | 实现输出重定向。输出重定向比输入重定向更常用。输出重定向使用户能把一个命令的输出重定向到一个文件里,而不是显示在屏幕上。很多情况下都可以使用这种功能。>实现“覆盖式”输出重定向;>>实现“追加式”输出重定向 |
2>或2>> | 实现错误重定向。类似地,2>是“覆盖式"的;2>>是“追加式”的 |
&> | 同时实现输出重定向和错误重定向 |
下面给出几个使用重定向操作的例子。
操作步骤2.6 使用重定向的例子
//快速建立MP3播放列表
find ~ -name *.mp3 > ~/cd.play.list
//使用输入重定向显示文件 ~/cd.play.list的行数
wc -l < ~/cd.play.list
//在用户自己的环境配置文件中追加定义HISTIGNORE和HISTCONTROL环境变量
//忽略ls和ll命令的命令历史记录,且不记录首字符为空格的命令
#echo 'export HISTIGNORE="ls:ll"' >> ~/.bash_profile
# echo 'export HISTCONTROL="ignorespace"' >> ~/.bash_profile
//使用Here文件在表文件后追加记录
# cat >> /etc/hosts << _END
> 192.168.0.77
> 192.168.0.88 db1
> 192.168.0.99 win1
> _END
//将ls命令的错误信息保存在err_file文件中(RANDOM是一个环境变量,其值是一个随机数)
# ls anaconda-ks.cfg $RANDOM 2> err_file
anaconda-ks.cfg
# cat err_file
//将ls命令的输出和错误信息保存在output_file中
#ls anaconda-ks.cfg $RANDOM &>output_file
# cat output_file
//屏蔽命令program的标准输出和错误输出(常用于Shell脚本)
# program &>/dev/null
//因为屏蔽了命令输出,可以通过显示特殊变量$?的值来判断上面的命令是否正确执行
//$?的值为0表示执行正确,而非0表示执行错误
# echo $?
127
(2)管道
许多Linux命令具有过滤特性,即一条命令通过标准输入端口接收一个文件中的数据,命令执行后产生的结果数据又通过标准输出端口送给后一条命令, 作为该命令的输入数据。后一条命令也是通过标准输入端口而接收输入数据。
Shell提供管道命令“|” 将这些命令前后衔接在一起, 形成个管道线, 格式为:
命令1|命令2|.....|命令n
管道线中的每一条命令都作为一个单独的进程运行,每一条命令的输出作为下一条命令的输入。由于管道线中的命令总是从左到右顺序执行的,因此管道线是单向的。
管道线的实现创建了Linux系统管道文件并进行重定向,但是管道不同于I/O重定向,输入重定向导致一个程序的标准输入来自某个文件,输出重定向是将一个程序的标准输出写到一个文件中,而管道是直接将一个程序的标准输出与另一个程序的标准输入相连接,不需要经过任何中间文件。下面给出几个使用管道的例子。
操作步骤2.7使用管道的例子
//1.以长格式递归的方式分屏显示/etc目录下的文件和目录列表
# ls -Rl /etc | less
//2.只列子目录
# ls -F | grep /$ 或# ls -l |grep "^d"
//3.将man的信息存为纯文本文件
$ man bash | col -b > bash.txt
//4.将myfile中的字符转化成大写字母并输出到文件MYFILE
$ cat myfile |tr 'a-z' 'A-Z > MYFILE
//5.查看系统中是否存在名为osmond用户账号
$ cat /etc/passwd | grep ^osmond
//6.为用户发送一个测试邮件
$ echo "test email" | mail -s "test" user@example.com
//7.统计当前目录下磁盘占用最多的10个一级子目录
$ du . --max-depth=1 |sort -m | head -11
//8.以降序方式显示使用磁盘空间最多的普通用户的前10名
# du -cks /home/* | sort -m | head -11
//9.按内存使用从大到小排列输出进程
#ps -e -o "%C : %p: %z : %a"|sort k5 -nr
//10.按CPU使用从大到小排列输出进程
#ps -e -o "%C: %p: %z :%a" | sort -nr
/11.查看系统中的某项服务(如sshd)是否正在监听客户的请求(即服务是否已启动)
# netstat -anp | grep "LISTEN" | grep "sshd"
//12.查看TCP连接的各种连接状态的连接数
# netstat -n | awk '/^tcp/ { S[$NF]} END {for(a in S) print a, S[a]}'
//13.从uptime的输出过滤出时间和3个平均负载字段
# uptime
13:23:16 up 48 days, 14:34, 1 user, load average: 0.11, 0.17, 0.10
# uptime | awk -F '[ ,] ' '{print $2" -"$11,$12,$13}'
13:22:26- 0.12 0.160.11
//14.从ifconfig命令的输出过滤出eth0网络接口当前的IPv4地址(4 种方法达到同样的目的)
# ifconfig eth0 | grep 'inet addr' |cut -d': '-f2 | cut -d' ' -fl
# ifconfig eth0 | grep 'inet'| awk -F '[:] ' '{print $4}'
# ifconfig eth0 | awk F: /inet/ {print $2}' | awk '{print $1}'
# ifconfig eth0 | grep 'inet ' | sed s/[a-zA-Z:] //g' | awk '{print $1}'
//15.从ip命令的输出过滤出eno16777736网络接口当前的IPv4地址
#ip a s eno16777736 | grep 'inet' |awk -F'[/] ' '{print $3}'
//16.检查系统是否支持硬件虚拟化
#fmt -3 /proc/cpuinfo | sort -u |egrep "vmx|svm"
(3)命令替换
Shell中的命令参数可以由另个命令执行的结果来替代。使用的格式如下:
$ cmd1 'cmd2 arguments'
//或
$ cmd1 $(cmd2 arguments)
其中,cmd2 arguments 的输出作为cmdl的参数。
注意:
cmd2要放在反引号“`”里。请注意反引号“`”和单引号“’”的区别,它们在功能上并不相同。
下面给出几个使用命令替换的例子。
操作步骤2.8 使用命令替换的例子
//压缩当前目录下的所有不是以.gz为后缀的文件
$ gzip `find. \! -name '*.gz' -print`
$ gzip $(find . \! -name '*.gz' -print)
//在输出命令中,双引号""中的命令替换将被解析
$ echo "You have `ls |wc -l` files in `pwd`"
$ echo "You have $(ls | wc -l) files in $(pwd)"
11查看包含date命令的RPM包的信息(命令替换可以嵌套使用,嵌套时只能使用$()的形式)
# rpm -qi $(rpm qf $(which date))
//生成带有日期和时间的文件名后缀
$ touch backup_$(date "%Y")
$ touch backup_$(date "%F")
$ touch backup_$(date "%F _%H%M")
$ll
-rw-------. 1 root root 1549 Feb 1 11:42 anaconda-ks.cfg
-rw-r--r--. 1 root root 0 Mar 12 17:16 cd.play.list
-rw-r--r--. 1 root root 16 Mar 12 17:25 err_file
-rw-r--r--. 1 root root 10 Mar 9 14:30 file2~
-rw-r--r--. 1 root root 42 Mar 9 15:31 file3
-rw-r--r--. 1 root root 96961 Mar 9 16:39 head
-rw-r--r--. 1 root root 1297 Mar 9 16:35 head.txt
-rw-r--r--. 1 root root 0 Mar 9 11:50 IMG_FILE
(4)命令组合
除了可以使用管道连接若干命令之外,还可以在一个命令行上使用若干Shell的元字符将若干命令组合在一起,下表中列出了这些命令组合的方法及其说明。
命令组合 | |
命令行形式 | 说明 |
CMDI;CMD2 | 顺序执行一组命令序列 |
{MD1:CMD2;} | 在当前Shell中执行一组命令序列 |
(CMD1;CMD2) | 在子Shell中执行一组命令序列 |
CMD1&&CMD2 | 与逻辑:当CMDI运行成功时才运行CMD2.这是一个“短路”操作,若CMDI执行失败则CMD2水远不会被执行 |
CMD1||CMD2 | 或逻辑:当CMDI运行失败时才运行CMD2.这是一个短路”操作,若CMD1执行成功则CMD2水远不会被执行 |
下面给出几个使用命令组合的例子。
操作步骤2.9 使用命令组合的例子
//顺序执行一组命令
$ date; pwd; ls
//将一组命令的所有输出存入日志文件(不使用命令组合时,仅将最后一个命令的输出进行重定向)
$ (date; who | wc -1) > ~/login-users.log
$ (date; pwd; ls)> ~/logfile
//若osmond用户在线,则发送内容为~/logfile的在线消息,否则为其发送邮件
# write osmond < ~/logfile || mail -s test osmond < ~/logfile
//若/etc目录下有文件的内容包含security, 就显示Found
# grep -lr 'security' /et/* > /dev/null && echo "Found."
//若/etc 目录下有文件的内容包含security, 就显示Found, 否则显示Not Found
# grep -lr 'security' /etc/* > /dev/null && echo "Found." \ || echo "Not Found."
//从/proc/loadavg的输出生成时间和3个平均负载字段
# echo -n "$(date %T)- "; cat /proc/loadavg |cut -d' -f1 -3
13:31:07 - 0.04 0.09 0.08
//生成一个脚本用于将系统的平均负载记录于日期命名的文件(文件名为MMDD的形式)
# mkdir /root/bin
#cat <<_END _> /root/bin/uptimelog
DIR=/root/uptimelogs/\$(date "%Y")
[-d \$DIR]|| mkdir -p \$DIR //若目录不存在就创建
(echo -n "$(date %T)- "; cat /proc/loadavg |cut-d' '-f1 -3)\\
>>\ SDIR/\S(date "%m%d")
_END_
# cat /root/bin/uptimelog
DIR=/root/uptimelogs/$(datce "%Y")
[ -d $DIR]|| mkdir –p $DIR
(echo -n "S(date %T)- "; cat /proc/loadavg | (cut -d' -f1 -3)\
>>$DIR/$(date "%m%d")
注意:
当Here文件的内容包含变量替换和命令替换时,必须对$使用转移符\$,否则会将变量替换和命令替换的结果存入输出重定向的文件。行尾的续行符\,也应该使用转义的\\,在使用Here文件形式生成Shell脚本时特别有用。