Bonky Zhu
If someone is able to show me that what I think or do is not right, I will happily change, for I seek the truth, by which no one was ever truly harmed. It is the person who continues in his self-deception and ignorance who is harmed.

Linux 管道命令&文件比对

image-20200113193629333

  • 管道命令仅会处理 stdout,对于 stderr 会予以忽略(如果想要使用 stderr 可以进行重定向 2>&1
  • 管道命令必须要能够接受来自前一个指令的数据成为 stdin 继续处理才行。

选择命令 cut,grep

cut 是以每一行为单位,选取出每一行满足的输出。

[dmtsai@study ~]$ cut -d'分隔字符' -f fields <==用于有特定分隔字符
[dmtsai@study ~]$ cut -c 字符区间                       <==用于排列整齐的讯息
选项与参数:
-d :后面接分隔字符。与 -f 一起使用;
-f :依据 -d 的分隔字符将一段讯息分区成为数段,用 -f 取出第几段的意思;
-c :以字符 (characters) 的单位取出固定字符区间;


下面是一些使用的例子:

[选取第三个和第五个 PATH 项]
➜  ~ echo ${PATH}
/opt/local/bin:/opt/local/sbin:/Library/Frameworks/Python.framework/Versions/3.6/bin:/Library/Frameworks/Python.framework/Versions/2.7/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin:/Applications/VMware Fusion.app/Contents/Public:/Library/TeX/texbin
➜  ~ echo ${PATH} | cut -d ':' -f 3,5
/Library/Frameworks/Python.framework/Versions/3.6/bin:/usr/local/bin

[选取第7个字符以后的内容]
➜  ~ ps
  PID TTY           TIME CMD
34927 ttys000    0:00.27 /bin/zsh -l
36366 ttys001    0:00.40 -zsh
➜  ~ ps | cut -c 7-
TTY           TIME CMD
ttys000    0:00.27 /bin/zsh -l
ttys001    0:00.41 -zsh
ttys001    0:00.00 cut -c 7-


grep 是取出满足条件的行,注意,搜寻的字符串支持基本的正则表达式

[dmtsai@study ~]$ grep [-acinv] [--color=auto] '搜寻字符串' filename
选项与参数:
-a :将 binary 文件以 text 文件的方式搜寻数据
-c :计算找到 '搜寻字符串' 的次数
-i :忽略大小写的不同,所以大小写视为相同
-n :顺便输出行号
-v :反向选择,亦即显示出没有 '搜寻字符串' 内容的那一行!
--color=auto :可以将找到的关键词部分加上颜色的显示喔


如果你想要杀死包含某个关键词进程,可以使用:

kill -9 `ps aux | grep  | cut -d ' ' -f 2`


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 就是啦!


下面是一些使用示例(由于 mac 上 sed 有点问题,这里使用的是 gsed):

1. 在第二行后面添加 helloworld
➜  ~ gsed -i '2a helloworld' ll.txt
➜  ~ cat ll.txt
root   admin   4.7K  1 14 09:13 Applications
root   wheel   102B  1 17  2019 Backup Extensions
helloworld
bonky  wheel   204B 12  7  2018 EFI
bonky  wheel   272B  9 26  2017 Extra
......

2. 删除1~3行
➜  ~ gsed '1,3d' ll.txt
bonky  wheel   204B 12  7  2018 EFI
bonky  wheel   272B  9 26  2017 Extra
root   wheel     0B  4  4  2014 Icon
......

3. 正则替换(貌似好像这个不支持加号)
➜  ~ gsed 's/root.*[BK]/deleted /g' ll.txt
deleted   1 14 09:13 Applications
deleted ackup Extensions
helloworld
bonky  wheel   204B 12  7  2018 EFI
bonky  wheel   272B  9 26  2017 Extra
deleted   4  4  2014 Icon


强大的选择工具 awk

awk 通常把文件分成列来处理,一般来说 awk 的格式如下:

awk '条件类型 1{动作 1} 条件类型 2{动作 2} ...' filename


注意,awk 使用的分隔符是空格或者 tab,所以如果你数据项里面有空格或者有为空的数据项,那么就会出现误判的情况。然后,awk 有一些非常中药内建变量:

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

所以我们可以这样子打印带行号,中间分隔符分开的数据:

➜  ~ ps | awk '{print NR FS $1}'
1 PID
2 413
3 416
4 1885


注意一点,awk 使用的是单引号,然后实际打印出的字符串用双引号表示

➜  ~ ps | awk '{print "Line " NR ":" FS $1}'
Line 1: PID
Line 2: 413
Line 3: 416
Line 4: 2126


下面我们来讲讲 awk 的逻辑运算,一个简单的写法如下,满足条件才会执行 print 动作(用这种方法我去除了第一行的 PID):

➜  ~ ps | awk '$1<99999 {print $1}' 413 416 2191 ➜ ~ ps | awk 'NR>1 {print $1}'
413
416
2470


因为 awk 默认是以空格为分隔符,所有我们自定义分隔符的时候会出现以下问题:

[dmtsai@study ~]$ cat /etc/passwd | awk '{FS=":"} $3 < 10 {print $1 " \t " $3}'
root:x:0:0:root:/root:/bin/bash
bin    1
daemon 2
......


因为我们读入第一行的时候,那些变数 \$1,\$2... 默认还是以空格键为分隔的,所以虽然我们定义了 FS=":" 了, 但是却仅能在第二行后才开始生效。我们只需要在前面加上 BEGIN 即可:

cat /etc/passwd | awk 'BEGIN {FS=":"} $3 < 10 {print $1 " \t " $3}'
root        0
bin         1
daemon  2


注意这里 BEGIN 代表的是开始之前执行的操作,同理 END 代表的是结束后进行的操作。

➜  ~ ps | awk 'BEGIN{print "THIS IS START"}  {print "Line " NR ":" FS $1} END{print "THIS IS END"}'
THIS IS START
Line 1: PID
Line 2: 413
Line 3: 416
Line 4: 2440
THIS IS END


我们可以使用 awk 进行表格处理:

image-20200114111201078

我们想计算他的总和然后格式化输出:

$ cat pay.txt | \
> awk 'NR==1{printf "%10s %10s %10s %10s %10s \n",$1,$2,$3,$4,"Total" }
> NR>=2{total = $2 + $3 + $4
> printf "%10s %10d %10d %10d %10.2f\n", $1, $2, $3, $4, total}'


输出结果如下:

image-20200114111757424

排序命令 sort,uniq,wc

首先我们来讲下 sort 指令:

$ sort [-fbMnrtuk] [file or stdin]
选项与参数:
-f :忽略大小写的差异,例如 A 与 a 视为编码相同;
-b :忽略最前面的空格符部分;
-M :以月份的名字来排序,例如 JAN, DEC 等等的排序方法;
-n :使用『纯数字』进行排序(默认是以文字型态来排序的);
-r :反向排序;
-u :就是 uniq ,相同的数据中,仅出现一行代表;
-t :分隔符,预设是用 [tab] 键来分隔;
-k :以那个区间 (field) 来进行排序的意思


下面是一个简单的简单的读取当前文件夹最大的10个文件或者文件夹(其实 sort 部分不要写这么麻烦,只不过练手而已):

``` shell
➜ ~ du -d 1 | sort -n -r -t " " -k 1 | head -10


然后就是 uniq 指令,其实如果对计数没有要求直接用 sort -u 也可以:

```shell
$ uniq [-ic]
选项与参数:
-i :忽略大小写字符的不同;
-c :进行计数


wc 的功能其实很简单就是计算文件信息:

$ wc [-lmw] [file ...]
选项与参数:
-l :仅列出行;
-w :仅列出多少字(英文单字);
-m :多少字符;


既然是文件,当然也可以加上重定向~

$ cat ll.txt > wc


双向重定向 tee

image-20200114114158095

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

$ tee [-a] file
选项与参数:
-a :以累加 (append) 的方式,将数据加入 file 当中!


字符处理 tr, join, paste, expand

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

$ tr [-ds] SET1 ... filename
选项与参数:
-d :删除讯息当中的 SET1 这个字符串;
-s :取代掉重复的字符!


下面是一个简单的用法,我们可以注意,下面的所有数字被替换成了问号,其次是 tr 后面的字符串是按照正则来匹配的:

➜  ~ cat ll.txt
total 128624
drwx------@   6 bonky  staff   204B 11 12 12:51 Applications
drwx------@   3 bonky  staff   102B  9  8 07:45 Applications (Parallels)
......

➜  ~ cat ll.txt| tr "[0-9]+" '?'
total ??????
drwx------@   ? bonky  staff   ???B ?? ?? ??:?? Applications
drwx------@   ? bonky  staff   ???B  ?  ? ??:?? Applications (Parallels)
......

➜  ~ cat ll.txt| tr -d "[0-9]+"
total
drwx------@    bonky  staff   B   : Applications
drwx------@    bonky  staff   B     : Applications (Parallels)


tr 的常用是进行 dos 系统的换行到 Linux 的转换:

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

  • Linux\Unix 系统中:每行结尾只有 "<换行>",即 "\n",即LF;
  • Windows 系统中:每行结尾是 "<回车><换行>",即 "\r\n",即CRLF;
  • Mac 系统中:每行结尾是 "<回车>",即 "\r",即 CR。

PS;在 Python 中读文件的时候推荐使用 open('a.txt', 'rU').read(),U 代表的是通用换行(把所有的换行符(LF,CR,CRLF)替换为 LF )

join 命令主要是处理两个文件当中,把相同字段的那一行合并为一行:

$ join [-ti12] file1 file2
选项与参数:
-t :join 默认以空格符分隔数据,并且比对第一个字段的数据, 如果两个文件相同,则将两笔数据联成一行,且第一个字段放在第一个!
-i :忽略大小写的差异;
-1 :这个是数字的 1 ,代表『第一个文件要用那个字段来分析』的意思;
-2 :代表『第二个文件要用那个字段来分析』的意思。


使用方法:

$ 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:$6$wtbCCce/PxMeE5wm$KE2IfSJr...:16559:0:99999:7:::
bin:*:16372:0:99999:7:::
daemon:*:16372:0:99999:7:::

$ join -t ':' -1 4 /etc/passwd -2 3 /etc/group | head -n 3
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:


paste 直接不管这么多,把相同的行粘在一起:

$ paste [-d] file1 file2
选项与参数:
-d :后面可以接分隔字符。预设是以 [tab] 来分隔的!
- :如果 file 部分写成 - ,表示来自 standard input 的资料的意思。


expand 的作用就是把 tab 转换为相同的空格键。

$ expand [-t] file
选项与参数:
-t :后面可以接数字。一般来说,一个 tab 按键可以用 8 个空格键取代。我们也可以自行定义一个 [tab] 按键代表多少个字符呢!


切分命令 split

split 的作用就是把大文件切分为较小的文件。

split [-bl] file PREFIX
选项与参数:
-b :后面可接欲分区成的文件大小,可加单位,例如 b, k, m 等;
-l :以行数来进行分区。
PREFIX :作为分后的文件名的前缀。


下面是一个示例:

$ split -b 1m  mpe


可以看到在同目录下出现了切分后的文件:

屏幕快照 2020-01-14 下午1.56.30

然后我们还可以利用 cat 命令合并,发现合并后的文件和原有文件是一样的:

$ cat mpe* > a.mp3


参数传递 xargs

xargs 的作用就是产生某个指令的参数,往往适用于非管道命令。

xargs [-0epn] command
选项与参数:
-0 :如果输入的 stdin 含有特殊字符,例如 `, \, 空格键等等字符时,这个 -0 参数可以将他还原成一般字符。这个参数可以用于特殊状态喔
-e :这个是 EOF (end of file) 的意思。后面可以接一个字符串,当 xargs 分析到这个字符串时,就会停止继续工作!
-p :在执行每个指令的 argument 时,都会询问使用者的意思;
-n :后面接次数,每次 command 指令执行时,要使用几个参数的意思。
当 xargs 后面没有接任何的指令时,默认是以 echo 来进行输出喔!


比如,我们都知道 ls 是非管道命令,a.txt 是我们给出的一个文件和目录列表,我们想要对给出的的列表进行 ls 操作:

➜  ~ cat a.txt | ls
Applications             Pictures                 go
Applications (Parallels) Public                   info
......


执行完 cat a.txt | ls,发现我们执行的实际上就是 ls 命令,和 a.txt 中的文件和目录没有什么关系。这时就是 xargs 大展身手的时候了:

➜  ~ cat a.txt | xargs -n 1 ls
OSProject
$RECYCLE.BIN
17500
Deep-Reinforcement-Learning-for-Dialogue-Generation-in-tensorflow-master
Neural-Chatbot-master
Thumbs.db
....


可以发现正如我们所期待的那样,对给出的的列表进行了 ls 操作。上面 -n 1 代表每次取一个参数(即每次取一行作为参数)。当然,如果不放心的话(可能执行 rm -rf 操作的时候中间混入了一个根目录),我们可以使用 -p 参数,每次执行命令的时候询问:

➜  ~ cat a.txt | xargs -p -n 1 ls
ls Applications?...y
Chrome Apps.localized   Xcode.app


减号 -

在 tar 命令中,减号 - 代表标准输入输出流。所以我们可以这么用:

$ tar -cvf - /home | tar -xvf - -C /tmp/homeback


上面的意思是:我将 /home 里面的文件给他打包,但打包的数据不是纪录到文件,而是传送到 stdout; 经过管线后,将 tar -cvf - /home 传送给后面的 tar -xvf -

文件比对 diff

$ diff [-bBi] from-file to-file
选项与参数:
from-file :一个档名,作为原始比对文件的档名;(老文件)
to-file :一个档名,作为目的比对文件的档名;(新文件)
注意,from-file 或 to-file 可以 - 取代,那个 - 代表『Standard input』之意。

-b :忽略一行当中,仅有多个空白的差异(例如 "about me" 与 "about      me" 视为相同
-B :忽略空白行的差异。
-i :忽略大小写的不同。


文件修改更新 patch

$ patch [-pN] filename < patch_file <==更新
$ patch [-pN] -R filename < patch_file <==还原
选项与参数: 
-p :后面可以接『取消几层目录』的意思。
-R :代表还原,将新的文件还原成原来旧的版本。


使用方法如下:首先第一步,利用 diff 制作 patch_file 文件,注意右边那个版本更新:

$ diff ll.txt a.txt 
1d0
< 总用量 1180 $ diff ll.txt a.txt > test.patch


这样子我们生成了 patch 文件,然后对 ll.txt 使用(如果用错了文件会报错):

$ patch ll.txt < test.patch
patching file ll.txt

$ patch a.txt < test.patch
patching file a.txt
Reversed (or previously applied) patch detected!  Assume -R? [n]

Share

You may also like...

发表评论