• 练习 8:更多的重定向和过滤:headtailawkgrepsed
    • 这样做
      • 你会看到什么
    • 解释
    • 附加题

    练习 8:更多的重定向和过滤:headtailawkgrepsed

    原文:Exercise 8. Bash: more on redirection and filtering: head, tail, awk, grep, sed

    译者:飞龙

    协议:CC BY-NC-SA 4.0

    自豪地采用谷歌翻译

    现在你试过了 Linux,我会介绍一下 Unix 的方式。注意看。

    这就是 Unix 的哲学:写一些程序,只做一件事,并且把它做好。编写程序,使其一起工作。编写程序来处理文本流,因为这是一个通用接口。

    实际上这意味着为了熟练使用 Linux,你需要知道如何从一个程序中获取输出,并将其提供给另一个程序,通常会在此过程中修改它。通常,你可以通过使用管道,将多个程序合并在一起,它允许将一个程序的输出连接到另一个程序。像这样:

    练习 8:更多的重定向和过滤:head,tail,awk,grep,sed - 图1

    这里发生的事情真的很简单。几乎每个 Linux 程序在启动时打开这三个文件:

    stdin - 标准输入。这是程序读取东西的地方。
    stdout - 标准输出。这是程序写出东西的地方。
    stderr - 标准错误。这是程序报错的地方。

    这就是它的读取方式:

    1. 启动程序 1
    2. 开始从键盘读取数据
    3. 开始向显示器写出错误
    4. 启动程序 2
    5. 开始从程序 1 读取输入
    6. 开始向显示器写出错误
    7. 启动程序 3
    8. 开始从程序 2 读取输入
    9. 开始向显示器写出错误
    10. 开始向显示器写出数据

    还有另一种方式来描绘发生的事情,如果你喜欢 South Park 风格的幽默,但要小心:看到的是不会是不可见的!警告!你无法忽略它。

    让我们考虑以下管道,它接受ls -al的输出,仅打印文件名和文件修改时间:

    1. ls -al | tr -s ' ' | cut -d ' ' -f 8,9

    这是所发生事情的概述:

    1. 启动 ls -al
    2. 获取当前目录中的文件列表
    3. 向显示器写出错误
    4. 向管道写出输出
    5. 启动 tr -s ' '
    6. 通过管道从 ls -al 读取输入
    7. 两个字段之间只保留一个空格
    8. 向显示器写出错误
    9. 向管道写出输出
    10. 启动 cut -d ' ' -f 8,9
    11. 通过管道从 tr -s ' ' 读取输入
    12. 只保留字段 8 9,扔掉其它东西
    13. 向显示器写出错误
    14. 向显示器写出输出

    更详细地说,这是每一步发生的事情:

    第一步:ls -al,我们获取了目录列表,每一列都叫做字段。

    1. user1@vm1:~$ ls -al
    2. total 52
    3. drwxr-xr-x 2 user1 user1 4096 Jun 18 14:16 .
    4. drwxr-xr-x 3 root root 4096 Jun 6 21:49 ..
    5. -rw------- 1 user1 user1 4865 Jun 15 19:34 .bash_history
    6. -rw-r--r-- 1 user1 user1 220 Jun 6 21:48 .bash_logout
    7. -rw-r--r-- 1 user1 user1 3184 Jun 14 12:24 .bashrc
    8. -rw-r--r-- 1 user1 user1 64 Jun 18 14:16 hello.txt
    9. -rw------- 1 user1 user1 89 Jun 18 16:26 .lesshst
    10. -rw-r--r-- 1 user1 user1 634 Jun 15 20:03 ls.out
    11. -rw-r--r-- 1 user1 user1 697 Jun 7 12:25 .profile
    12. -rw-r--r-- 1 user1 user1 741 Jun 7 12:19 .profile.bak
    13. -rw-r--r-- 1 user1 user1 741 Jun 7 13:12 .profile.bak1
    14. -rw------- 1 user1 user1 666 Jun 18 14:16 .viminfo

    第二步:ls -al | tr -s ' ',我们在两个字段之间只保留,因为cut不能将多个空格理解为一种方式,来分离多个字段。

    1. user1@vm1:~$ ls -al | tr -s ' '
    2. total 52
    3. drwxr-xr-x 2 user1 user1 4096 Jun 18 14:16 .
    4. drwxr-xr-x 3 root root 4096 Jun 6 21:49 ..
    5. -rw------- 1 user1 user1 4865 Jun 15 19:34 .bash_history
    6. -rw-r--r-- 1 user1 user1 220 Jun 6 21:48 .bash_logout
    7. -rw-r--r-- 1 user1 user1 3184 Jun 14 12:24 .bashrc
    8. -rw-r--r-- 1 user1 user1 64 Jun 18 14:16 hello.txt
    9. -rw------- 1 user1 user1 89 Jun 18 16:26 .lesshst
    10. -rw-r--r-- 1 user1 user1 634 Jun 15 20:03 ls.out
    11. -rw-r--r-- 1 user1 user1 697 Jun 7 12:25 .profile
    12. -rw-r--r-- 1 user1 user1 741 Jun 7 12:19 .profile.bak
    13. -rw-r--r-- 1 user1 user1 741 Jun 7 13:12 .profile.bak1
    14. -rw------- 1 user1 user1 666 Jun 18 14:16 .viminfo

    第三步:我们只保留字段 8 和 9,它们是我们想要的。

    1. user1@vm1:~$ ls -al | tr -s ' ' | cut -d ' ' -f 8,9
    2. 14:16 .
    3. 21:49 ..
    4. 19:34 .bash_history
    5. 21:48 .bash_logout
    6. 12:24 .bashrc
    7. 14:16 hello.txt
    8. 16:26 .lesshst
    9. 20:03 ls.out
    10. 12:25 .profile
    11. 12:19 .profile.bak
    12. 13:12 .profile.bak1
    13. 14:16 .viminfo

    现在你学到了,如何从一个程序获取输入,并将其传给另一个程序,并且如何转换它。

    这样做

    1. 1: ls -al | head -n 5
    2. 2: ls -al | tail -n 5
    3. 3: ls -al | awk '{print $8, $9}'
    4. 4: ls -al | awk '{print $9, $8}'
    5. 5: ls -al | awk '{printf "%-20.20s %s\n",$9, $8}'
    6. 6: ls -al | grep bash
    7. 7: ls -al > ls.out
    8. 8: cat ls.out
    9. 9: cat ls.out | sed 's/bash/I replace this!!!/g'

    你会看到什么

    1. user1@vm1:~$ ls -al | head -n 5
    2. total 52
    3. drwxr-xr-x 2 user1 user1 4096 Jun 18 14:16 .
    4. drwxr-xr-x 3 root root 4096 Jun 6 21:49 ..
    5. -rw------- 1 user1 user1 4865 Jun 15 19:34 .bash_history
    6. -rw-r--r-- 1 user1 user1 220 Jun 6 21:48 .bash_logout
    7. user1@vm1:~$ ls -al | tail -n 5
    8. -rw-r--r-- 1 user1 user1 636 Jun 18 17:52 ls.out
    9. -rw-r--r-- 1 user1 user1 697 Jun 7 12:25 .profile
    10. -rw-r--r-- 1 user1 user1 741 Jun 7 12:19 .profile.bak
    11. -rw-r--r-- 1 user1 user1 741 Jun 7 13:12 .profile.bak1
    12. -rw------- 1 user1 user1 666 Jun 18 14:16 .viminfo
    13. user1@vm1:~$ ls -al | awk '{print $8, $9}'
    14. 14:16 .
    15. 21:49 ..
    16. 19:34 .bash_history
    17. 21:48 .bash_logout
    18. 12:24 .bashrc
    19. 14:16 hello.txt
    20. 16:26 .lesshst
    21. 17:52 ls.out
    22. 12:25 .profile
    23. 12:19 .profile.bak
    24. 13:12 .profile.bak1
    25. 14:16 .viminfo
    26. user1@vm1:~$ ls -al | awk '{print $9, $8}'
    27. . 14:16
    28. .. 21:49
    29. .bash_history 19:34
    30. .bash_logout 21:48
    31. .bashrc 12:24
    32. hello.txt 14:16
    33. .lesshst 16:26
    34. ls.out 17:52
    35. .profile 12:25
    36. .profile.bak 12:19
    37. .profile.bak1 13:12
    38. .viminfo 14:16
    39. user1@vm1:~$ ls -al | awk '{printf "%-20.20s %s\n",$9, $8}'
    40. . 14:16
    41. .. 21:49
    42. .bash_history 19:34
    43. .bash_logout 21:48
    44. .bashrc 12:24
    45. hello.txt 14:16
    46. .lesshst 16:26
    47. ls.out 17:52
    48. .profile 12:25
    49. .profile.bak 12:19
    50. .profile.bak1 13:12
    51. .viminfo 14:16
    52. user1@vm1:~$ ls -al | grep bash
    53. -rw------- 1 user1 user1 4865 Jun 15 19:34 .bash_history
    54. -rw-r--r-- 1 user1 user1 220 Jun 6 21:48 .bash_logout
    55. -rw-r--r-- 1 user1 user1 3184 Jun 14 12:24 .bashrc
    56. user1@vm1:~$ ls -al > ls.out
    57. user1@vm1:~$ cat ls.out
    58. total 48
    59. drwxr-xr-x 2 user1 user1 4096 Jun 18 14:16 .
    60. drwxr-xr-x 3 root root 4096 Jun 6 21:49 ..
    61. -rw------- 1 user1 user1 4865 Jun 15 19:34 .bash_history
    62. -rw-r--r-- 1 user1 user1 220 Jun 6 21:48 .bash_logout
    63. -rw-r--r-- 1 user1 user1 3184 Jun 14 12:24 .bashrc
    64. -rw-r--r-- 1 user1 user1 64 Jun 18 14:16 hello.txt
    65. -rw------- 1 user1 user1 89 Jun 18 16:26 .lesshst
    66. -rw-r--r-- 1 user1 user1 0 Jun 18 17:53 ls.out
    67. -rw-r--r-- 1 user1 user1 697 Jun 7 12:25 .profile
    68. -rw-r--r-- 1 user1 user1 741 Jun 7 12:19 .profile.bak
    69. -rw-r--r-- 1 user1 user1 741 Jun 7 13:12 .profile.bak1
    70. -rw------- 1 user1 user1 666 Jun 18 14:16 .viminfo
    71. user1@vm1:~$ cat ls.out | sed 's/bash/I replace this!!!/g'
    72. total 48
    73. drwxr-xr-x 2 user1 user1 4096 Jun 18 14:16 .
    74. drwxr-xr-x 3 root root 4096 Jun 6 21:49 ..
    75. -rw------- 1 user1 user1 4865 Jun 15 19:34 .I replace this!!!_history
    76. -rw-r--r-- 1 user1 user1 220 Jun 6 21:48 .I replace this!!!_logout
    77. -rw-r--r-- 1 user1 user1 3184 Jun 14 12:24 .I replace this!!!rc
    78. -rw-r--r-- 1 user1 user1 64 Jun 18 14:16 hello.txt
    79. -rw------- 1 user1 user1 89 Jun 18 16:26 .lesshst
    80. -rw-r--r-- 1 user1 user1 0 Jun 18 17:53 ls.out
    81. -rw-r--r-- 1 user1 user1 697 Jun 7 12:25 .profile
    82. -rw-r--r-- 1 user1 user1 741 Jun 7 12:19 .profile.bak
    83. -rw-r--r-- 1 user1 user1 741 Jun 7 13:12 .profile.bak1
    84. -rw------- 1 user1 user1 666 Jun 18 14:16 .viminfo

    解释

    • 只打印目录列表中的前 5 个条目。
    • 只打印目录列表中的后 5 个条目。
    • 只打印修改时间和文件名。注意我如何使用awk,这比cut更聪明。这里的区别就是,cut只能将单个符号(我们这里是空格)理解为一种方式,来分离字段(字段分隔符),awk将任意数量的空格和 TAB 看做文件分隔符,所以没有必要使用tr来消除不必要的空格。
    • 按此顺序打印文件名和修改时间。这又是cat不能做的事情。
    • 工整地打印文件名和修改时间。注意现在输出如何变得更清晰。
    • 仅打印目录列表中包含bash的行。
    • 将目录列表的输出写入文件ls.out
    • 打印出ls.outcat是最简单的可用程序,允许你打印出一个文件,没有更多了。尽管如此简单,但在构建复杂管道时非常有用。
    • 打印出ls.out,将所有的bash条目替换为I replace this!!!sed是一个强大的流编辑器,它非常非常非常有用。

    附加题

    • 打开headtailawkgrepsed的手册页。不要害怕,只要记住手册页面总是在那里。有了一些实践,你将能够实际了解他们。
    • 查找grep选项,能够打印它找到的那行之前,或之后的一行。
    • 使用 Google 搜索awk printf命令,尝试了解它如何工作。
    • 阅读 The Useless Use of Cat Award。尝试那里的一些例子。