GolangHub

公众号:Golang语言开发栈

Linux管道命令

Linux管道命令

管道命令使用的是“|”这个界定符,仅能处理标准输出(standard output),对于错误输出(standard error output)会予以忽略,必须要能够接收来自上一个命令的数据作为标准输入(standard input)继续处理。

管道命令的处理示意图

选取命令:cut,grep

cut

这个命令可以将一段信息的某一段“切”出来,处理的信息是以“行”为单位。

格式:

cut -d ‘分隔字符’ -f fields

cut -c 字符范围

参数:

-d:后面接分隔符,与-f一起使用。

-f:依据-d的分隔符将一段信息切割成为数段,用-f取出第几段的意思。

-c:以字符的单位取出固定字符区间。

示例1:

将PATH变量取出,我要找到第五个路径。

1
2
3
4
[root@localhost ~]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
[root@localhost ~]# echo $PATH | cut -d ':' -f 5
/root/bin

如上所示,我们是以“:”分隔符取出第5段数据。

如果我们想要取出第3和第5段数据,如下所示:

1
2
[root@localhost ~]# echo $PATH |cut -d ':' -f 3,5
/usr/sbin:/root/bin

示例2:

将export输出的信息取得第12个字符以后的所有字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[root@localhost ~]# export
declare -x HISTCONTROL="ignoredups"
declare -x HISTSIZE="1000"
declare -x HOME="/root"
declare -x HOSTNAME="localhost.localdomain"
declare -x LANG="zh_CN.UTF-8"
declare -x LC_CTYPE="zh_CN.UTF-8"
declare -x LESSOPEN="||/usr/bin/lesspipe.sh %s"
declare -x LOGNAME="root"
declare -x LS_COLORS="rs=0:di=38;5;27:ln=38;5;51:mh=44;38;5;15:pi=40;38;5;11:so=38;5;13:do=38;5;5:bd=48;5;232;38;5;11:cd=48;5;232;38;5;3:or=48;5;232;38;5;9:mi=05;48;5;232;38;5;15:su=48;5;196;38;5;15:sg=48;5;11;38;5;16:ca=48;5;196;38;5;226:tw=48;5;10;38;5;16:ow=48;5;10;38;5;21:st=48;5;21;38;5;15:ex=38;5;34:*.tar=38;5;9:*.tgz=38;5;9:*.arc=38;5;9:*.arj=38;5;9:*.taz=38;5;9:*.lha=38;5;9:*.lz4=38;5;9:*.lzh=38;5;9:*.lzma=38;5;9:*.tlz=38;5;9:*.txz=38;5;9:*.tzo=38;5;9:*.t7z=38;5;9:*.zip=38;5;9:*.z=38;5;9:*.Z=38;5;9:*.dz=38;5;9:*.gz=38;5;9:*.lrz=38;5;9:*.lz=38;5;9:*.lzo=38;5;9:*.xz=38;5;9:*.bz2=38;5;9:*.bz=38;5;9:*.tbz=38;5;9:*.tbz2=38;5;9:*.tz=38;5;9:*.deb=38;5;9:*.rpm=38;5;9:*.jar=38;5;9:*.war=38;5;9:*.ear=38;5;9:*.sar=38;5;9:*.rar=38;5;9:*.alz=38;5;9:*.ace=38;5;9:*.zoo=38;5;9:*.cpio=38;5;9:*.7z=38;5;9:*.rz=38;5;9:*.cab=38;5;9:*.jpg=38;5;13:*.jpeg=38;5;13:*.gif=38;5;13:*.bmp=38;5;13:*.pbm=38;5;13:*.pgm=38;5;13:*.ppm=38;5;13:*.tga=38;5;13:*.xbm=38;5;13:*.xpm=38;5;13:*.tif=38;5;13:*.tiff=38;5;13:*.png=38;5;13:*.svg=38;5;13:*.svgz=38;5;13:*.mng=38;5;13:*.pcx=38;5;13:*.mov=38;5;13:*.mpg=38;5;13:*.mpeg=38;5;13:*.m2v=38;5;13:*.mkv=38;5;13:*.webm=38;5;13:*.ogm=38;5;13:*.mp4=38;5;13:*.m4v=38;5;13:*.mp4v=38;5;13:*.vob=38;5;13:*.qt=38;5;13:*.nuv=38;5;13:*.wmv=38;5;13:*.asf=38;5;13:*.rm=38;5;13:*.rmvb=38;5;13:*.flc=38;5;13:*.avi=38;5;13:*.fli=38;5;13:*.flv=38;5;13:*.gl=38;5;13:*.dl=38;5;13:*.xcf=38;5;13:*.xwd=38;5;13:*.yuv=38;5;13:*.cgm=38;5;13:*.emf=38;5;13:*.axv=38;5;13:*.anx=38;5;13:*.ogv=38;5;13:*.ogx=38;5;13:*.aac=38;5;45:*.au=38;5;45:*.flac=38;5;45:*.mid=38;5;45:*.midi=38;5;45:*.mka=38;5;45:*.mp3=38;5;45:*.mpc=38;5;45:*.ogg=38;5;45:*.ra=38;5;45:*.wav=38;5;45:*.axa=38;5;45:*.oga=38;5;45:*.spx=38;5;45:*.xspf=38;5;45:"
declare -x MAIL="/var/spool/mail/root"
declare -x OLDPWD
declare -x PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/mnt/share/Go/bin:/usr/local/go/bin"
declare -x PWD="/root"
declare -x SHELL="/bin/bash"
declare -x SHLVL="1"
declare -x SSH_CLIENT="10.58.95.76 62278 22"
declare -x SSH_CONNECTION="10.58.95.76 62278 10.58.95.29 22"
declare -x SSH_TTY="/dev/pts/0"
declare -x TERM="xterm-256color"
declare -x USER="root"
declare -x XDG_DATA_DIRS="/root/.local/share/flatpak/exports/share/:/var/lib/flatpak/exports/share/:/usr/local/share/:/usr/share/"
declare -x XDG_RUNTIME_DIR="/run/user/0"
declare -x XDG_SESSION_ID="471"
declare -x gowork="/mnt/share/Go/src/github.com/frank"
declare -x work="/mnt/share"

上面每个数据都是排列整齐的输出,如果我们不想要declare -x(注意空格)时,就得这么做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[root@localhost ~]# export | cut -c 12-
HISTCONTROL="ignoredups"
HISTSIZE="1000"
HOME="/root"
HOSTNAME="localhost.localdomain"
LANG="zh_CN.UTF-8"
LC_CTYPE="zh_CN.UTF-8"
LESSOPEN="||/usr/bin/lesspipe.sh %s"
LOGNAME="root"
LS_COLORS="rs=0:di=38;5;27:ln=38;5;51:mh=44;38;5;15:pi=40;38;5;11:so=38;5;13:do=38;5;5:bd=48;5;232;38;5;11:cd=48;5;232;38;5;3:or=48;5;232;38;5;9:mi=05;48;5;232;38;5;15:su=48;5;196;38;5;15:sg=48;5;11;38;5;16:ca=48;5;196;38;5;226:tw=48;5;10;38;5;16:ow=48;5;10;38;5;21:st=48;5;21;38;5;15:ex=38;5;34:*.tar=38;5;9:*.tgz=38;5;9:*.arc=38;5;9:*.arj=38;5;9:*.taz=38;5;9:*.lha=38;5;9:*.lz4=38;5;9:*.lzh=38;5;9:*.lzma=38;5;9:*.tlz=38;5;9:*.txz=38;5;9:*.tzo=38;5;9:*.t7z=38;5;9:*.zip=38;5;9:*.z=38;5;9:*.Z=38;5;9:*.dz=38;5;9:*.gz=38;5;9:*.lrz=38;5;9:*.lz=38;5;9:*.lzo=38;5;9:*.xz=38;5;9:*.bz2=38;5;9:*.bz=38;5;9:*.tbz=38;5;9:*.tbz2=38;5;9:*.tz=38;5;9:*.deb=38;5;9:*.rpm=38;5;9:*.jar=38;5;9:*.war=38;5;9:*.ear=38;5;9:*.sar=38;5;9:*.rar=38;5;9:*.alz=38;5;9:*.ace=38;5;9:*.zoo=38;5;9:*.cpio=38;5;9:*.7z=38;5;9:*.rz=38;5;9:*.cab=38;5;9:*.jpg=38;5;13:*.jpeg=38;5;13:*.gif=38;5;13:*.bmp=38;5;13:*.pbm=38;5;13:*.pgm=38;5;13:*.ppm=38;5;13:*.tga=38;5;13:*.xbm=38;5;13:*.xpm=38;5;13:*.tif=38;5;13:*.tiff=38;5;13:*.png=38;5;13:*.svg=38;5;13:*.svgz=38;5;13:*.mng=38;5;13:*.pcx=38;5;13:*.mov=38;5;13:*.mpg=38;5;13:*.mpeg=38;5;13:*.m2v=38;5;13:*.mkv=38;5;13:*.webm=38;5;13:*.ogm=38;5;13:*.mp4=38;5;13:*.m4v=38;5;13:*.mp4v=38;5;13:*.vob=38;5;13:*.qt=38;5;13:*.nuv=38;5;13:*.wmv=38;5;13:*.asf=38;5;13:*.rm=38;5;13:*.rmvb=38;5;13:*.flc=38;5;13:*.avi=38;5;13:*.fli=38;5;13:*.flv=38;5;13:*.gl=38;5;13:*.dl=38;5;13:*.xcf=38;5;13:*.xwd=38;5;13:*.yuv=38;5;13:*.cgm=38;5;13:*.emf=38;5;13:*.axv=38;5;13:*.anx=38;5;13:*.ogv=38;5;13:*.ogx=38;5;13:*.aac=38;5;45:*.au=38;5;45:*.flac=38;5;45:*.mid=38;5;45:*.midi=38;5;45:*.mka=38;5;45:*.mp3=38;5;45:*.mpc=38;5;45:*.ogg=38;5;45:*.ra=38;5;45:*.wav=38;5;45:*.axa=38;5;45:*.oga=38;5;45:*.spx=38;5;45:*.xspf=38;5;45:"
MAIL="/var/spool/mail/root"
OLDPWD
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/mnt/share/Go/bin:/usr/local/go/bin"
PWD="/root"
SHELL="/bin/bash"
SHLVL="1"
SSH_CLIENT="10.58.95.76 62278 22"
SSH_CONNECTION="10.58.95.76 62278 10.58.95.29 22"
SSH_TTY="/dev/pts/0"
TERM="xterm-256color"
USER="root"
XDG_DATA_DIRS="/root/.local/share/flatpak/exports/share/:/var/lib/flatpak/exports/share/:/usr/local/share/:/usr/share/"
XDG_RUNTIME_DIR="/run/user/0"
XDG_SESSION_ID="471"
gowork="/mnt/share/Go/src/github.com/frank"
work="/mnt/share"

如上所示,用-c可以处理比较具有格式的输出数据。我们还可以指定某个范围的值,例如12-20的字符,就是cut -c 12-20

示例3:

用last在显示的登录者信息中仅保留下用户名

1
2
3
4
5
6
[root@localhost ~]# last
root pts/0 10.58.95.76 Mon Apr 16 17:03 still logged in
root pts/1 10.58.95.76 Wed Apr 4 10:28 - 10:00 (4+23:31)
root pts/0 10.58.95.76 Tue Apr 3 15:33 - 11:10 (19:37)
root :0 :0 Tue Apr 3 15:32 still logged in
reboot system boot 3.10.0-693.17.1. Tue Apr 3 15:29 - 17:17 (13+01:48)

last可以输出“帐号/终端机/来源/日期时间”的数据,并且是排列整齐的。

以上输出结果可以发现第一个空格分隔的字段代表帐号,所以我们使用以下命令:

1
2
3
4
5
6
[root@localhost ~]# last | cut -d ' ' -f 1
root
root
root
root
reboot

但是仔细观察发现root和pts/0之间有好几个空格,所以如果想找出pts/0使用last | cut -d ' ' -f 1,2得到的结果并非我们想要的。

总结:

cut主要用途在于将同一行里面的数据进行分解,一般使用在分析一些数据的时候,比如分析日志log文件。

grep

grep基础用法

这个命令是分析一行信息中是否包含我们想要的信息,如果包含就将该行显示出来。

格式:

grep [-acinv] [--color=auto] '查找的字符串' filename

参数:

-a:将binary文件以text文件的方式查找数据。

-c:计算找到”查找字符串“的次数

-i:忽略大小写。

-n:输出行号。

-v:反向选择,即显示出没有”查找字符串“内容的那一行。

--color=auto:可以将找到的关键字部分加上颜色显示。

示例1:

将last当中有出现root的那一行取出来。

1
2
3
4
5
6
[root@localhost ~]# last | grep 'root'
root pts/0 10.58.95.76 Mon Apr 16 17:03 still logged in
root pts/1 10.58.95.76 Wed Apr 4 10:28 - 10:00 (4+23:31)
root pts/0 10.58.95.76 Tue Apr 3 15:33 - 11:10 (19:37)
root :0 :0 Tue Apr 3 15:32 still logged in
root pts/0 10.58.95.76 Fri Mar 30 16:09 - 11:07 (3+18:57)

示例2:

与示例1相反,将last当中没有出现root的那一行取出来。

1
2
3
4
5
[root@localhost ~]# last | grep -v 'root'
reboot system boot 3.10.0-693.17.1. Tue Apr 3 15:29 - 17:44 (13+02:15)
reboot system boot 3.10.0-693.17.1. Mon Feb 26 18:17 - 15:01 (35+20:43)
(unknown :0 :0 Sun Feb 11 18:16 - 09:19 (8+15:03)
reboot system boot 3.10.0-514.el7.x Thu Feb 8 10:58 - 15:01 (54+04:02)

示例3:

在last的输出信息中,只要有root就取出,并且只取第一列。

1
2
3
4
5
[root@localhost ~]# last | grep 'root' | cut -d ' ' -f 1
root
root
root
root

示例4:

取出/etc/man_db.conf内含MANPATH的那几行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@localhost ~]# grep --color=auto 'MANPATH' /etc/man_db.conf
# MANDATORY_MANPATH manpath_element
# MANPATH_MAP path_element manpath_element
# every automatically generated MANPATH includes these fields
#MANDATORY_MANPATH /usr/src/pvm3/man
MANDATORY_MANPATH /usr/man
MANDATORY_MANPATH /usr/share/man
MANDATORY_MANPATH /usr/local/share/man
# set up PATH to MANPATH mapping
# *PATH* -> *MANPATH*
MANPATH_MAP /bin /usr/share/man
MANPATH_MAP /usr/bin /usr/share/man
MANPATH_MAP /sbin /usr/share/man
MANPATH_MAP /usr/sbin /usr/share/man
MANPATH_MAP /usr/local/bin /usr/local/man
MANPATH_MAP /usr/local/bin /usr/local/share/man
MANPATH_MAP /usr/local/sbin /usr/local/man
MANPATH_MAP /usr/local/sbin /usr/local/share/man
MANPATH_MAP /usr/X11R6/bin /usr/X11R6/man
MANPATH_MAP /usr/bin/X11 /usr/X11R6/man
MANPATH_MAP /usr/games /usr/share/man
MANPATH_MAP /opt/bin /opt/man
MANPATH_MAP /opt/sbin /opt/man
# *MANPATH* -> *CATPATH*

grep高级用法

格式:

grep [-A] [-B] [--color=auto] '查找的字符串' filename

参数:

-A:后面可加数字,为after的意思,除了列出该行外,后续的n行也列出来。

-B:后面可加数字,为befor的意思,除了列出该行外,前面的n行也列出来。

--color=auto:可将正确的那个选取数据列出颜色。

示例1:

用dmesg列出内核信息,再以grep找出包含eth的那行。

1
2
3
[root@localhost ~]# dmesg | grep 'eth'
[ 1.480975] e1000 0000:00:03.0 eth0: (PCI:33MHz:32-bit) 08:00:27:3d:a2:77
[ 1.480981] e1000 0000:00:03.0 eth0: Intel(R) PRO/1000 Network Connection

示例2:

承接上面的示例,将找到的关键字显色,并且加上行号。

1
2
3
[root@localhost ~]# dmesg | grep --color=auto -n 'eth'
398:[ 1.480975] e1000 0000:00:03.0 eth0: (PCI:33MHz:32-bit) 08:00:27:3d:a2:77
399:[ 1.480981] e1000 0000:00:03.0 eth0: Intel(R) PRO/1000 Network Connection

示例3:

承接示例2,将关键字所在行的前3行和后2行也一起显示出来。

1
2
3
4
5
6
7
8
[root@localhost ~]# dmesg | grep --color=auto -n -A2 -B3 'eth'
395-[ 1.023883] e1000: Intel(R) PRO/1000 Network Driver - version 7.3.21-k8-NAPI
396-[ 1.023885] e1000: Copyright (c) 1999-2006 Intel Corporation.
397-[ 1.064784] libata version 3.00 loaded.
398:[ 1.480975] e1000 0000:00:03.0 eth0: (PCI:33MHz:32-bit) 08:00:27:3d:a2:77
399:[ 1.480981] e1000 0000:00:03.0 eth0: Intel(R) PRO/1000 Network Connection
400-[ 1.481003] ata_piix 0000:00:01.1: version 2.13
401-[ 1.485167] scsi host0: ata_piix

sed工具

sed本身也是一个管道命令,可以分析standard input的数据,而且sed还可以将数据进行替换、删除、新增、选取特定行等功能。

格式:

sed [-nefr] [动作]

参数:

-n:使用安静(silent)模式。在一般sed的用法中,所有来自STDIN的数据一般都会被列出到屏幕上。但如果加上-n参数后,则只经过sed特殊处理的那一行(或者操作)才会被列出来。

-e:直接在命令行模式上进行sed的动作编辑。

-f:直接将sed的动作写在一个文件内,-f filename则可以执行filename内的sed动作。

-r:sed的动作支持的是扩展型正则表达式的语法(默认是基础正则表达式)。

-i:直接修改读取文件内容,而不是由屏幕输出。

动作说明:

[n1[,n2]] function

n1,n2:不见得会存在,一般代表选择进行动作的行数,举例来说,如果我的动作是需要在10到20行之间进行的,则”10,20[动作行为]“

function有下面这些参数:

a:新增,a的后面可以接字符串,而这些字符串会在新的一行出现(目前的下一行)。

c:替换,c的后面可以接字符串,这些字符串可以替换n1,n2之间的行。

d:删除,因为是删除,所以d后面通常不接任何参数。

i:插入,i的后面可以接字符串,而这些字符串会在新的一行出现(目前的上一行)。

p:打印,也就是将某个选择的数据打印出来。通常p会与参数sed -n一起运行。

s:替换,可以直接进行替换工作。通常这个s的动作可以搭配正则表达式。例如:1,20s/old/new/g就是。

示例1:

将/etc/passwd的内容列出并且打印行号,同时请将第2-5行删除。

1
2
3
4
5
6
7
8
[root@localhost test6]# nl /etc/passwd | sed '2,5d'
1 root:x:0:0:root:/root:/bin/bash
6 sync:x:5:0:sync:/sbin:/bin/sync
7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
8 halt:x:7:0:halt:/sbin:/sbin/halt
9 mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
10 operator:x:11:0:operator:/root:/sbin/nologin
...

通过上面可知,sed的动作为’2,5d’,d就是删除。因为2-5行被删除了,所以显示的数据没有2-5行。另外需要注意的是,原本应该是sed -e才对,没有-e也行。同时还要注意的是,sed后面接的动作,请务必使用单引号括起来

多说一点,删除2-5行,使用sed '2,5d',那么只删除2行怎么办,使用sed '2d',如果要删除3到最后一行,使用sed '3,$d',$代表最后一行。

示例2:

承上例,在第二行后(即加在第三行)加上“eat food?”字样。

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@localhost test6]# nl /etc/passwd | sed '2a eat food?'
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin
eat food?
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin
4 adm:x:3:4:adm:/var/adm:/sbin/nologin
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
6 sync:x:5:0:sync:/sbin:/bin/sync
7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
8 halt:x:7:0:halt:/sbin:/sbin/halt
9 mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
10 operator:x:11:0:operator:/root:/sbin/nologin
...

由上可知,在a后面加上的字符串就已经出现在第2行后面。如果是要在第2行之前加呢?sed '2i eat food?'就好了。就是将a变成i。那如果要是增加两行以上呢?

示例3:

承上例,在第2行后面加入两行字。

1
2
3
4
5
6
7
8
9
10
[root@localhost test6]# nl /etc/passwd | sed '2a drink tea?\
> drink water?'
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin
drink tea?
drink water?
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin
4 adm:x:3:4:adm:/var/adm:/sbin/nologin
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
...

由上可知,如果想新增多行,需要在每一行之间写上反斜杠“\”

示例4:

我想将第2-5行的内容替换为“No 2-5 number”。

1
2
3
4
5
6
[root@localhost test6]# nl /etc/passwd | sed '2,5c No 2-5 number'
1 root:x:0:0:root:/root:/bin/bash
No 2-5 number
6 sync:x:5:0:sync:/sbin:/bin/sync
7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
...

由上可知,我们替换了整行的数据。

示例5:

仅列出/etc/passwd文件的第5-7行。

1
2
3
4
[root@localhost test6]# nl /etc/passwd | sed -n '5,7p'
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
6 sync:x:5:0:sync:/sbin:/bin/sync
7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown

上述操作,有个重要的参数-n,没有这个参数,会重复输出。

部分数据的查找与替换功能

格式:

sed ‘s/要被替换的字符串/新字符串/g’

示例1:

1
2
3
[root@localhost test6]# /sbin/ifconfig enp0s3 | grep 'netmask' | \
> sed 's/inet//g' | sed 's/netmask.*$//g'
10.58.93.49

awk:好用的数据处理工具

awk将一行分成数个”字段“来处理,而默认的分隔符是空格键和[Tab]键。

格式:

awk ‘条件类型1{动作1} 条件类型2{动作2} …’ filename

示例1:

取出前5行登录者的用户名和IP地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@localhost test6]# last -n 5
root pts/1 10.58.95.76 Tue Apr 17 09:58 still logged in
root pts/0 10.58.95.76 Mon Apr 16 17:03 still logged in
root pts/1 10.58.95.76 Wed Apr 4 10:28 - 10:00 (4+23:31)
root pts/0 10.58.95.76 Tue Apr 3 15:33 - 11:10 (19:37)
root :0 :0 Tue Apr 3 15:32 still logged in

wtmp begins Mon Jan 22 11:38:09 2018

[root@localhost test6]# last -n 5 | awk '{print $1 "\t" $3}'
root 10.58.95.76
root 10.58.95.76
root 10.58.95.76
root 10.58.95.76
root :0

wtmp Mon

以上示例显示,通过print的功能(动作)将字段数据列出来,字段的分隔则以空格或Tab来隔开。因为不论哪一行我都要处理,所以不需要条件类型的限制。我们还可以看到,在每一行的每个字段都会有变量名称,那就是$1,$2等变量名称。以上示例来说,root是$1,因为它是第一列。10.58.95.76是第3列,所以它是$3,后面以此类推。还有个变量,那就是$0,$0代表一整行数据的意思。以上示例来说,第一行的$0代表的就是”root…“那一行。

由此可见,上面5行中,awk的处理流程是这样的:

  1. 读入第一行,并将第一行的数据填入$0,$1,$2等变量中。
  2. 依据条件类型限制,判断是否需要进行后面的动作。
  3. 做完所有的动作和条件类型。
  4. 若还有后续的”行“的数据,则重复执行1-3的步骤,直到所有的数据都读完为止。

经过上面这些步骤,可以知道awk是以行为一次处理单元,以字段为最小的处理单元。

awk怎么知道我到底这个数据有几行几列呢?这就需要awk的内置变量帮忙了。

如下表所示:

变量名称 代表意义
NF 每一行($0)拥有的字段总和
NR 目前awk所处理的第几行数据
FS 目前的分隔字符,默认是空格键

示例2:

承接示例1,我们想要:

列出每一行的帐号(就是$1)

列出目前处理的行数(就是awk的内置变量NR)

说明该行有多少字段(就是awk的内置变量NF)

则可以如下操作:

1
2
3
4
5
6
7
8
[root@localhost test6]# last -n 5 | awk '{print $1 "\t lines:" NR "\t columes:" NF }'
root lines:1 columes:10
root lines:2 columes:10
root lines:3 columes:10
root lines:4 columes:10
root lines:5 columes:10
lines:6 columes:0
wtmp lines:7 columes:7

注意:

  • awk后续的所有动作是以单引号括起来的,非变量文字部分需要使用双引号括起来
  • 在awk内的NF,NR等变量都要大写,且不需要美元符$

条件类型

awk的逻辑运算符:

示例1:

在/etc/passwd当中是以冒号”:“来作为分隔符的,该文件中第一个字段为帐号,第三个字段为uid。假设我要查阅第三列小于10以下的数据,并且仅列出帐号和第三列。可以这么做:

1
2
3
4
5
6
7
8
9
10
[root@localhost test6]# cat /etc/passwd | awk '{FS=":"} $3<10 {print $1 "\t" $3}'
root:x:0:0:root:/root:/bin/bash
bin 1
daemon 2
adm 3
lp 4
sync 5
shutdown 6
halt 7
mail 8

仔细观察上面显示结果,你会发现第一行数据没有按照我们想要的格式显示出来。这是因为我们读入第一行的时候,那些变量$1,$2…默认还是以空格键分隔的,所以虽然我们定义了FS=”:”了,但是只能在第2行后开始生效。怎么办?我们可以预先设置awk的变量。利用BEGIN这个关键字,这样做:

1
2
3
4
5
6
7
8
9
10
11
[root@localhost test6]# cat /etc/passwd | \
> awk 'BEGIN {FS=":"} $3<10 {print $1 "\t" $3}'
root 0
bin 1
daemon 2
adm 3
lp 4
sync 5
shutdown 6
halt 7
mail 8

注意:

  • 所有awk的动作,即在{}内的动作,如果有需要多个命令辅助时,可利用多个分号”;“间隔。或者直接以[Enter]按键来隔开每个命令。
  • 当然有BEGIN就有END,awk特殊模式END大家可以自己了解一下。
  • 不管是想要用好awk还是上面介绍的sed,都需要大家掌握正则表达式

排序命令:sort,wc,uniq

sort

sort可以依据不同的数据类型来排序。例如数字与文字的排序就不一样

格式:

sort [-fbMnrtuk][file or stdin]

参数:

-f:忽略大小写的差异,例如A与a视为编码相同

-b:忽略最前面的空格符部分

-M:以月份的名字来排序,例如JAN,DEC等的排序方法

-n:使用“纯数字”进行排序(默认是以文字类型来排序的)

示例1:个人帐号都记录在/etc/passwd下,请将帐号进行排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
cat /etc/passwd | sort
#
#
# Note that this file is consulted directly only when the system is running
# Open Directory.
# Open Directory.
# See the opendirectoryd(8) man page for additional information about
# User Database
# in single-user mode. At other times this information is provided by
##
##
_amavisd:*:83:83:AMaViS Daemon:/var/virusmails:/usr/bin/false
_analyticsd:*:263:263:Analytics Daemon:/var/db/analyticsd:/usr/bin/false
_appleevents:*:55:55:AppleEvents Daemon:/var/empty:/usr/bin/false
_applepay:*:260:260:applepay Account:/var/db/applepay:/usr/bin/false
_appowner:*:87:87:Application Owner:/var/empty:/usr/bin/false
_appserver:*:79:79:Application Server:/var/empty:/usr/bin/false
_appstore:*:33:33:Mac App Store Service:/var/empty:/usr/bin/false
_ard:*:67:67:Apple Remote Desktop:/var/empty:/usr/bin/false
_assetcache:*:235:235:Asset Cache Service:/var/empty:/usr/bin/false
_astris:*:245:245:Astris Services:/var/db/astris:/usr/bin/false
_atsserver:*:97:97:ATS Server:/var/empty:/usr/bin/false
_avbdeviced:*:229:-2:Ethernet AVB Device Daemon:/var/empty:/usr/bin/false
_calendar:*:93:93:Calendar:/var/empty:/usr/bin/false
_captiveagent:*:258:258:captiveagent:/var/empty:/usr/bin/false
...中间部分省略...
_wwwproxy:*:252:252:WWW Proxy:/var/empty:/usr/bin/false
_xserverdocs:*:251:251:macOS Server Documents Service:/var/empty:/usr/bin/false
daemon:*:1:1:System Services:/var/root:/usr/bin/false
nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false
root:*:0:0:System Administrator:/var/root:/bin/sh

由上面的数据可以看出,sort是默认“以第一个”数据来排序,而且默认是以“文字”类型来排序,所以是由a开始排到最后。

示例2:/etc/passwd内容是以:来分隔的,我想以第三列来排序,该怎么办?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
cat /etc/passwd | sort -t':' -k3
#
#
# Note that this file is consulted directly only when the system is running
# Open Directory.
# Open Directory.
# See the opendirectoryd(8) man page for additional information about
# User Database
# in single-user mode. At other times this information is provided by
##
##
nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false
root:*:0:0:System Administrator:/var/root:/bin/sh
_taskgated:*:13:13:Task Gate Daemon:/var/empty:/usr/bin/false
daemon:*:1:1:System Services:/var/root:/usr/bin/false
_softwareupdate:*:200:200:Software Update Service:/var/db/softwareupdate:/usr/bin/false
_coreaudiod:*:202:202:Core Audio Daemon:/var/empty:/usr/bin/false
_screensaver:*:203:203:Screensaver:/var/empty:/usr/bin/false
_locationd:*:205:205:Location Daemon:/var/db/locationd:/usr/bin/false
省略...

以上数据,看到daemon…这一行了吧。是不是有疑问?没错啦,按照“文字”类型排序,本来就是这样。如果想要以“数字”类型排序,需要使用-n参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
cat /etc/passwd | sort -t':' -k3 -n
nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false
#
#
# Note that this file is consulted directly only when the system is running
# Open Directory.
# Open Directory.
# See the opendirectoryd(8) man page for additional information about
# User Database
# in single-user mode. At other times this information is provided by
##
##
root:*:0:0:System Administrator:/var/root:/bin/sh
daemon:*:1:1:System Services:/var/root:/usr/bin/false
_uucp:*:4:4:Unix to Unix Copy Protocol:/var/spool/uucp:/usr/sbin/uucico
_taskgated:*:13:13:Task Gate Daemon:/var/empty:/usr/bin/false
_networkd:*:24:24:Network Services:/var/networkd:/usr/bin/false
_installassistant:*:25:25:Install Assistant:/var/empty:/usr/bin/false
_lp:*:26:26:Printing Services:/var/spool/cups:/usr/bin/false
_postfix:*:27:27:Postfix Mail Server:/var/spool/postfix:/usr/bin/false

示例3:利用last将输出的数据仅取账号,并加以排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
last | cut -d' ' -f1 | sort

reboot
reboot
reboot
reboot
reboot
reboot
reboot
reboot
reboot
reboot
reboot
reboot
reboot
root
root
root
root
root
shutdown
shutdown
shutdown
shutdown
shutdown
shutdown
frank
uniq

如果我排序完成了,想把重复的数据只显示一个出来,可以使用uniq

格式:

uniq [-ic]

参数:

-i:忽略大小写

-c:进行计数

示例1:使用last将帐号列出,仅取出帐号列,进行排序后仅取出一位。

1
2
3
4
5
6
7
last | cut -d' ' -f1 | sort | uniq

reboot
root
shutdown
frank
wtmp

示例2:承上例,如果我还想知道每个人的登录总次数

1
2
3
4
5
6
7
last | cut -d' ' -f1 | sort | uniq -c
1
13 reboot
5 root
6 shutdown
540 frank
1 wtmp
wc

如果想知道/etc/man.config这个文件里面有多少字?多少行?多少个字符的话,可以怎么做呢?其实可以利用wc这个命令来完成。它可以帮助我们计算输出的信息的整体数据。

格式:

wc [-lwm]

参数:

-l:仅列出行

-w:仅列出多少字(英文单字)

-m:多少字符

示例1:那个/etc/man.config里面到底有多少相关字、行、字符数?

1
2
cat /etc/man.conf|wc
140 720 4574

输出的三个数字中,分别代表行,字数,字符数

示例2:我知道使用last可以输出登录者,但是last最后两行并非帐号内容,那么请问,我该如何使用一行命令取得这个月登录系统的总人数?

1
2
last | grep '[a-zA-Z]' | grep -v 'wtmp' | wc -l
564

使用grep取出非空白行,再去掉wtmp那一行,再计算行数,可得到想要的数据。

双向重定向tee

我们知道>会将数据流整个传送给文件或设备,因此我们除非去读取文件或设备,否则就无法继续利用这个数据流。那么万一我想要这个数据流的处理过程中将某段信息存下来,应该怎么做?利用tee就可以,我们可以简单地看一下tee的工作流程示意图

tee会同时将数据流送给文件和屏幕(screen),而输出到屏幕的,其实就是stdout,可以让下个命令继续处理。

格式:

tee [-a] file

参数:

-a:以累加(append)的方式,将数据加入file当中

示例1:将last的输出存一份到last.list文件中,并在屏幕上列出第一列

1
2
3
4
5
6
7
last | tee last.list | cut -d' ' -f1
frank
frank
frank
frank
frank
省略...

示例2:将ls的数据存一份到~/homefile,同时屏幕上也有输出信息。

1
ls -l /home | tee ~/homefile | more

示例3:将ls的数据追加到~/homefile,同时屏幕上也有输出信息

1
ls -l / | tee -a ~/homefile | more

注意上面-a参数,加上这个参数则能累加,否则会覆盖文件原来的内容。

切割命令:split

如果你有一个文件太大,导致一些便携设备无法复制的问题。使用split可以把一个大文件依据文件大小或行数来切割成小文件。

格式:

split [-bl] file PREFIX

参数:

-b:后面可接欲切割成的文件大小,可加单位,例如b,k,m等

-l:以行数来进行切割

PREFIX:代表前导符,可作为切割文件的前导文字

示例1:我本地有一个java_error_in_phpstorm_31797.log文件,如果想要分割成10k一个文件,怎么办?

1
split -b 10k java_error_in_phpstorm_31797.log java_error_log

查看分割后的文件

1
2
3
4
5
6
7
8
9
10
ll -k java_error_log*
-rw-r--r-- 1 frank staff 10240 5 14 18:15 java_error_logaa
-rw-r--r-- 1 frank staff 10240 5 14 18:15 java_error_logab
-rw-r--r-- 1 frank staff 10240 5 14 18:15 java_error_logac
-rw-r--r-- 1 frank staff 10240 5 14 18:15 java_error_logad
-rw-r--r-- 1 frank staff 10240 5 14 18:15 java_error_logae
-rw-r--r-- 1 frank staff 10240 5 14 18:15 java_error_logaf
-rw-r--r-- 1 frank staff 10240 5 14 18:15 java_error_logag
-rw-r--r-- 1 frank staff 10240 5 14 18:15 java_error_logah
-rw-r--r-- 1 frank staff 945 5 14 18:15 java_error_logai

由以上数据可知,我们只给文件名的前导文字,小文件就会以“前导文字aa,前导文字ab”等方式来新建小文件。

示例2:如何将分隔后的小文件合并为一个大文件?

1
cat java_error_log* >> java_error_log_back

就是利用数据流重定向实现。

示例3:将使用ls -al /输出的信息,没10行记录切割成一个文件

1
ls -al / | split -l 10 - lsroot

查看切割后的结果

1
2
3
4
5
6
wc -l lsroot*
10 lsrootaa
10 lsrootab
10 lsrootac
4 lsrootad
34 total

以上命令行,重点是那个-,如果需要stdout/stdin,但是偏偏又没有文件,有的只是-时,那么-就会被当成stdout/stdin

参数代换:xargs

xargs可以读入stdin的数据,并且以空格符或断行字符进行分辨,将stdin的数据分隔成为arguments。因为是以空格符作为分隔,所以,如果有一些文件名或者其他意义的名词内含有空格符的时候,xargs可能就会误判了。

格式:

xargs [-oepn] command

参数:

-o:如果输入的stdin含有特殊字符,例如`,\,空格键等字符时,这个参数可以将它还原成一般字符。这个参数可以用于特殊状态。

-E:这个是EOF(end of file)的意思。后面可以接一个字符串,当xargs分析到这个字符串时,就会停止继续工作。

-p:在执行每个命令的参数时,都会询问用户的意思。

-n:后面接次数,每次command命令执行时,要使用几个参数的意思。

示例1:将/etc/passwd内的第一列取出,仅取三行,仅用finger这个命令将每个帐号内容显示出来。

1
2
3
4
5
6
7
cut -d':' -f1 /etc/passwd | grep '_' | head -n3 | xargs finger
Login: _installassistant Name: Install Assistant
Directory: /var/empty Shell: /usr/bin/false
Never logged in.
No Mail.
No Plan.
...下面省略...

示例2:同上例,但是每次执行finger时,都要询问用户是否操作?

1
cut -d':' -f1 /etc/passwd | grep '_' | head -n3 | xargs -p finger

示例3:将所有/etc/passwd内的帐号都以finger查阅,但一次仅查阅5个帐号。

1
cut -d':' -f1 /etc/passwd | grep '_' | xargs -p -n 5 finger

示例4:同上例,但是当分析到lp就结束这串命令

1
cut -d':' -f1 /etc/passwd | grep '_' | xargs -p -E'lp' finger

这里要注意的是参数-e和后面接的字符串紧密相连(中间没有空格)

示例5:找出/sbin下面具有特殊权限的文件名,并使用ls -l列出详细属性

1
2
3
4
5
6
7
find /sbin -perm +7000 | ls -l
total 571056
drwx------ 4 frank staff 128 8 16 2016 Applications
drwx------+ 4 frank staff 128 5 14 00:48 Desktop
drwx------+ 11 frank staff 352 4 27 17:51 Documents
drwx------+ 29 frank staff 928 5 14 17:18 Downloads
...省略...

以上数据列出的是root所在目录下的文件,因为ll(ls -l)并不是管道命令。

1
find /sbin -perm +7000 | xargs ls -l

以上示例说明,很多命令不支持管道命令,我们可以通过xargs来提供该命令引用stdin之用。

字符转换命令:tr,col,join,paste,expand

tr

tr可以用来删除一段信息当中的文字,或者是进行文字信息的替换。

格式:

tr [-ds] SET1…

参数:

-d:删除信息当中的SET1这个字符串

-s:替换掉重复的字符

示例1:将last输出的信息中所有的小写字符变成大写字符

1
last | tr '[a-z]' '[A-Z]'

示例2:将/etc/passwd输出的信息中的冒号(:)删除

1
cat /etc/passwd | tr -d ':'

示例3:将/etc/passwd转存成dos断行到/root/passwd中,再将^M符号删除

1
cp /etc/passwd /root/passwd && unix2dos /root/passwd
1
2
3
file /etc/passwd /root/passwd
/etc/passwd: ASCII text
/root/passwd: ASCII text, with CRLF line terminators

上面列出的第二行内容就是DOS断行

1
cat /root/passwd | tr -d '\r' > /root/passwd.linux

上面的\r就是DOS的断行字符,也就是说^M可以用\r替代。

1
2
3
4
ll /etc/passwd /root/passwd*
-rw-r--r-- 1 root root 1463 5月 16 14:41 /etc/passwd
-rw-r--r-- 1 root root 1493 5月 16 17:30 /root/passwd
-rw-r--r-- 1 root root 1463 5月 16 17:33 /root/passwd.linux

上面是处理之后的结果,发现文件大小就和原来的/etc/passwd一致了。

col

格式:

col [-xb]

参数:

-x:将tab键转换成对等的空格键

-b:在文字内有反斜杠(/)时,仅保留反斜杠最后接的那个字符

示例1:使用cat -A显示出所有特殊键,最后以col[tab]转成空白

1
cat -A /etc/man_db.conf

以上返回数据中的^I的符号,那就是tab

使用col -xtab替换为空格键

1
cat /etc/man_db.conf | col -x | cat -A | more

示例2:将col/etc/man_db.conf转存为/root/col.man的纯文本文件

1
/etc/man_db.conf col > /root/col.man

使用vim查看/root/col.man会发现有很多特殊字符,下面我们把它转存为纯文本文件

1
/etc/man_db.conf col | col -b > /root/col.man
join

join是处理两个文件之间的数据,而且主要是将两个文件当中有相同数据的那一行加在一起。

格式:

join [-ti12] file1 file2

参数:

-t:join默认以空格符分隔数据,并且对比“第一个字段”的数据,如果两个文件相同,则将两条数据连成一行,且第一个字段放在第一个

-i:忽略大小写

-1:代表第一个文件要用哪个字段来分析

-2:代表第二个文件要用哪个字段来分析

示例1:用root身份,将/etc/passwd/etc/shadow相关数据整合成一列

1
2
3
4
5
6
7
8
9
10
head -n 3 /etc/passwd /etc/shadow
==> /etc/passwd <==
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin

==> /etc/shadow <==
root:$1$xaW/sNX3$dhWOwNRvJACohFlbSt7Qx1:17666:0:99999:7:::
bin:*:16659:0:99999:7:::
daemon:*:16659:0:99999:7:::

以上数据可以看出,两个文件最左边字段都是帐号,并且以:分隔。

1
2
3
4
5
6
join -t ':' /etc/passwd /etc/shadow
root:x:0:0:root:/root:/bin/bash:$1$xaW/sNX3$dhWOwNRvJACohFlbSt7Qx1:17666:0:99999:7:::
bin:x:1:1:bin:/bin:/sbin/nologin:*:16659:0:99999:7:::
daemon:x:2:2:daemon:/sbin:/sbin/nologin:*:16659:0:99999:7:::
adm:x:3:4:adm:/var/adm:/sbin/nologin:*:16659:0:99999:7:::
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin:*:16659:0:99999:7:::

通过上面这个操作,我们可以将两个文件的第一个字段相同的行整合成一行

示例2:我们知道/etc/passwd的第四个字段是GID,那个GID记录在/etc/group当中的第三个字段,请问如何将两个文件整合?

1
2
3
4
5
6
7
8
9
10
head -n 3 /etc/passwd /etc/group
==> /etc/passwd <==
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin

==> /etc/group <==
root:x:0:
bin:x:1:
daemon:x:2:

从上面数据可以看出,确实有相同的部分。下面我们来整合起来:

1
2
3
4
5
6
7
8
9
10
join -t ':' -1 4 /etc/passwd -2 3 /etc/group
0:root:x:0:root:/root:/bin/bash:root:x:
1:bin:x:1:bin:/bin:/sbin/nologin:bin:x:
2:daemon:x:2:daemon:/sbin:/sbin/nologin:daemon:x:
4:adm:x:3:adm:/var/adm:/sbin/nologin:adm:x:
join: /etc/passwd:6: is not sorted: sync:x:5:0:sync:/sbin:/bin/sync
7:lp:x:4:lp:/var/spool/lpd:/sbin/nologin:lp:x:
join: /etc/group:11: is not sorted: wheel:x:10:
99:nobody:x:99:Nobody:/:/sbin/nologin:nobody:x:
170:avahi-autoipd:x:170:Avahi IPv4LL Stack:/var/lib/avahi-autoipd:/sbin/nologin:avahi-autoipd:x:

与上例相同,相同的字段部分被移到最前面了,所以第2个文件的相同内容就没有再显示。

paste

paste将两个文件的同一行连接成一行,且中间以tab隔开。

格式:

paste [-d] file1 file2

参数:

-d:后面可以接分隔字符,默认是以tab分隔

-:如果file部分写成-,表示来自stdin的数据的意思

示例1:将/etc/passwd/etc/shadow同一行粘贴在一起。

1
2
3
4
5
6
7
8
paste /etc/passwd /etc/shadow
root:x:0:0:root:/root:/bin/bash root:$1$xaW/sNX3$dhWOwNRvJACohFlbSt7Qx1:17666:0:99999:7:::
bin:x:1:1:bin:/bin:/sbin/nologin bin:*:16659:0:99999:7:::
daemon:x:2:2:daemon:/sbin:/sbin/nologin daemon:*:16659:0:99999:7:::
adm:x:3:4:adm:/var/adm:/sbin/nologin adm:*:16659:0:99999:7:::
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin lp:*:16659:0:99999:7:::
sync:x:5:0:sync:/sbin:/bin/sync sync:*:16659:0:99999:7:::
省略...

以上数据可以看出,同一行中间是以tab分割的。

示例2:先使用cat/etc/group读出,再和示例1粘贴在一起,且仅取出前三行。

1
2
3
4
cat /etc/group|paste /etc/passwd /etc/shadow -|head -n 3
root:x:0:0:root:/root:/bin/bash root:$1$xaW/sNX3$dhWOwNRvJACohFlbSt7Qx1:17666:0:99999:7::: root:x:0:
bin:x:1:1:bin:/bin:/sbin/nologin bin:*:16659:0:99999:7::: bin:x:1:
daemon:x:2:2:daemon:/sbin:/sbin/nologin daemon:*:16659:0:99999:7::: daemon:x:2:

以上示例,主要是-,代表标准输出stdin。

expand

expand就是将tab转换成空格键

格式:

expand [-t] file

参数:

-t:后面可以接数字,一般一个tab可以用8个空格键替换。

更多精彩内容,请关注公众号 Golang语言开发栈公众号二维码
感谢赞赏.

Welcome to my other publishing channels