天天看點

笨辦法學 Linux 8~11練習 8:更多的重定向和過濾:head,tail,awk,grep,sed 練習 9:Bash:任務控制,jobs,fg 練習 10:Bash:程式退出代碼(傳回狀态)練習 11:總結

練習 8:更多的重定向和過濾:

head

tail

awk

grep

sed

原文: Exercise 8. Bash: more on redirection and filtering: head, tail, awk, grep, sed 譯者: 飛龍 協定: CC BY-NC-SA 4.0 自豪地采用 谷歌翻譯

現在你試過了 Linux,我會介紹一下 Unix 的方式。注意看。

這就是 Unix 的哲學:寫一些程式,隻做一件事,并且把它做好。編寫程式,使其一起工作。編寫程式來處理文本流,因為這是一個通用接口。

實際上這意味着為了熟練使用 Linux,你需要知道如何從一個程式中擷取輸出,并将其提供給另一個程式,通常會在此過程中修改它。通常,你可以通過使用管道,将多個程式合并在一起,它允許将一個程式的輸出連接配接到另一個程式。像這樣:

這裡發生的事情真的很簡單。幾乎每個 Linux 程式在啟動時打開這三個檔案:

stdin

- 标準輸入。這是程式讀取東西的地方。

stdout

- 标準輸出。這是程式寫出東西的地方。

stderr

- 标準錯誤。這是程式報錯的地方。

這就是它的讀取方式:

啟動程式 1 
    開始從鍵盤讀取資料
    開始向顯示器寫出錯誤
    啟動程式 2 
        開始從程式 1 讀取輸入
        開始向顯示器寫出錯誤
        啟動程式 3 
            開始從程式 2 讀取輸入
            開始向顯示器寫出錯誤
            開始向顯示器寫出資料           

還有另一種方式來描繪發生的事情,如果你喜歡 South Park 風格的幽默,但要小心:看到的是不會是不可見的!

警告!你無法忽略它

讓我們考慮以下管道,它接受

ls -al

的輸出,僅列印檔案名和檔案修改時間:

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

這是所發生事情的概述:

啟動 ls -al
    擷取目前目錄中的檔案清單
    向顯示器寫出錯誤
    向管道寫出輸出
    啟動 tr -s ' '
        通過管道從 ls -al 讀取輸入
        兩個字段之間隻保留一個空格
        向顯示器寫出錯誤
        向管道寫出輸出
        啟動 cut -d ' ' -f 8,9
            通過管道從 tr -s ' ' 讀取輸入
            隻保留字段 8 和 9,扔掉其它東西
            向顯示器寫出錯誤
            向顯示器寫出輸出           

更詳細地說,這是每一步發生的事情:

第一步:

ls -al

,我們擷取了目錄清單,每一列都叫做字段。

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

第二步:

ls -al | tr -s ' '

,我們在兩個字段之間隻保留,因為

cut

不能将多個空格了解為一種方式,來分離多個字段。

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

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

user1@vm1:~$ ls -al | tr -s ' ' | cut -d ' ' -f 8,9

14:16 .
21:49 ..
19:34 .bash_history
21:48 .bash_logout
12:24 .bashrc
14:16 hello.txt
16:26 .lesshst
20:03 ls.out
12:25 .profile
12:19 .profile.bak
13:12 .profile.bak1
14:16 .viminfo           

現在你學到了,如何從一個程式擷取輸入,并将其傳給另一個程式,并且如何轉換它。

這樣做

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

你會看到什麼

user1@vm1:~$ ls -al | head -n 5
total 52
drwxr-xr-x 2 user1 user1 4096 Jun 18 14:16 .
drwxr-xr-x 3 root  root  4096 Jun  6 21:49 ..
-rw------- 1 user1 user1 4865 Jun 15 19:34 .bash_history
-rw-r--r-- 1 user1 user1  220 Jun  6 21:48 .bash_logout
user1@vm1:~$ ls -al | tail -n 5
-rw-r--r-- 1 user1 user1  636 Jun 18 17:52 ls.out
-rw-r--r-- 1 user1 user1  697 Jun  7 12:25 .profile
-rw-r--r-- 1 user1 user1  741 Jun  7 12:19 .profile.bak
-rw-r--r-- 1 user1 user1  741 Jun  7 13:12 .profile.bak1
-rw------- 1 user1 user1  666 Jun 18 14:16 .viminfo
user1@vm1:~$ ls -al | awk '{print $8, $9}'

14:16 .
21:49 ..
19:34 .bash_history
21:48 .bash_logout
12:24 .bashrc
14:16 hello.txt
16:26 .lesshst
17:52 ls.out
12:25 .profile
12:19 .profile.bak
13:12 .profile.bak1
14:16 .viminfo
user1@vm1:~$ ls -al | awk '{print $9, $8}'

. 14:16
.. 21:49
.bash_history 19:34
.bash_logout 21:48
.bashrc 12:24
hello.txt 14:16
.lesshst 16:26
ls.out 17:52
.profile 12:25
.profile.bak 12:19
.profile.bak1 13:12
.viminfo 14:16

user1@vm1:~$ ls -al | awk '{printf "%-20.20s %s\n",$9, $8}'

.                    14:16
..                   21:49
.bash_history        19:34
.bash_logout         21:48
.bashrc              12:24
hello.txt            14:16
.lesshst             16:26
ls.out               17:52
.profile             12:25
.profile.bak         12:19
.profile.bak1        13:12
.viminfo             14:16
user1@vm1:~$ ls -al | grep bash
-rw------- 1 user1 user1 4865 Jun 15 19:34 .bash_history
-rw-r--r-- 1 user1 user1  220 Jun  6 21:48 .bash_logout
-rw-r--r-- 1 user1 user1 3184 Jun 14 12:24 .bashrc
user1@vm1:~$ ls -al > ls.out
user1@vm1:~$ cat ls.out
total 48
drwxr-xr-x 2 user1 user1 4096 Jun 18 14:16 .
drwxr-xr-x 3 root  root  4096 Jun  6 21:49 ..
-rw------- 1 user1 user1 4865 Jun 15 19:34 .bash_history
-rw-r--r-- 1 user1 user1  220 Jun  6 21:48 .bash_logout
-rw-r--r-- 1 user1 user1 3184 Jun 14 12:24 .bashrc
-rw-r--r-- 1 user1 user1   64 Jun 18 14:16 hello.txt
-rw------- 1 user1 user1   89 Jun 18 16:26 .lesshst
-rw-r--r-- 1 user1 user1    0 Jun 18 17:53 ls.out
-rw-r--r-- 1 user1 user1  697 Jun  7 12:25 .profile
-rw-r--r-- 1 user1 user1  741 Jun  7 12:19 .profile.bak
-rw-r--r-- 1 user1 user1  741 Jun  7 13:12 .profile.bak1
-rw------- 1 user1 user1  666 Jun 18 14:16 .viminfo
user1@vm1:~$ cat ls.out | sed  's/bash/I replace this!!!/g'
total 48
drwxr-xr-x 2 user1 user1 4096 Jun 18 14:16 .
drwxr-xr-x 3 root  root  4096 Jun  6 21:49 ..
-rw------- 1 user1 user1 4865 Jun 15 19:34 .I replace this!!!_history
-rw-r--r-- 1 user1 user1  220 Jun  6 21:48 .I replace this!!!_logout
-rw-r--r-- 1 user1 user1 3184 Jun 14 12:24 .I replace this!!!rc
-rw-r--r-- 1 user1 user1   64 Jun 18 14:16 hello.txt
-rw------- 1 user1 user1   89 Jun 18 16:26 .lesshst
-rw-r--r-- 1 user1 user1    0 Jun 18 17:53 ls.out
-rw-r--r-- 1 user1 user1  697 Jun  7 12:25 .profile
-rw-r--r-- 1 user1 user1  741 Jun  7 12:19 .profile.bak
-rw-r--r-- 1 user1 user1  741 Jun  7 13:12 .profile.bak1
-rw------- 1 user1 user1  666 Jun 18 14:16 .viminfo           

解釋

  • 隻列印目錄清單中的前 5 個條目。
  • 隻列印目錄清單中的後 5 個條目。
  • 隻列印修改時間和檔案名。注意我如何使用

    awk

    ,這比

    cut

    更聰明。這裡的差別就是,

    cut

    隻能将單個符号(我們這裡是空格)了解為一種方式,來分離字段(字段分隔符),

    awk

    将任意數量的空格和 TAB 看做檔案分隔符,是以沒有必要使用

    tr

    來消除不必要的空格。
  • 按此順序列印檔案名和修改時間。這又是

    cat

    不能做的事情。
  • 工整地列印檔案名和修改時間。注意現在輸出如何變得更清晰。
  • 僅列印目錄清單中包含

    bash

    的行。
  • 将目錄清單的輸出寫入檔案

    ls.out

  • 列印出

    ls.out

    cat

    是最簡單的可用程式,允許你列印出一個檔案,沒有更多了。盡管如此簡單,但在建構複雜管道時非常有用。
  • ls.out

    ,将所有的

    bash

    條目替換為

    I replace this!!!

    sed

    是一個強大的流編輯器,它非常非常非常有用。

附加題

  • 打開

    head

    tail

    awk

    grep

    sed

    的手冊頁。不要害怕,隻要記住手冊頁面總是在那裡。有了一些實踐,你将能夠實際了解他們。
  • 查找

    grep

    選項,能夠列印它找到的那行之前,或之後的一行。
  • 使用 Google 搜尋

    awk printf

    指令,嘗試了解它如何工作。
  • 閱讀 The Useless Use of Cat Award 。嘗試那裡的一些例子。

練習 9:Bash:任務控制,

jobs

fg

Exercise 9. Bash: job control, jobs, fg

Linux是一個

多任務

作業系統。這意味着有許多程式同時運作。從使用者的角度來看,這意味着你可以同時運作幾個程式,而且 bash 肯定有工具,為你控制多個任務的執行。為了能夠使用此功能,你需要學習以下指令:

  • <CTRL> + z

    - 将目前運作的程式放在背景。
  • jobs

    - 列出所有背景程式。
  • fg

    - 把程式帶到前台。

    fg

    接受一個數字作為參數,它可以從

    jobs

    中擷取數,或者如果無參數調用,則将最後一個挂起的程式帶到前台。
  • ctrl + c

    - 一次性停止執行目前運作的程式。雖然我不會在這個練習中使用它,但我必須說,這可能是非常有用的。

現在,你将學習如何使用 bash 内置的工具來控制程式的執行。

1: less -S .profile
 2: <CTRL+z>
 3: less -S .bashrc
 4: <CTRL+z>
 5: less -S .bash_history
 6: <CTRL+z>
 7: jobs
 8: fg
 9: q
10: fg
11: q
12: fg
13: q
14: fg
15: jobs           

user1@vm1:~$ less -S .profile
# exists.
# see /usr/share/doc/bash/examples/startup-files for
# the files are located in the bash-doc package.

# the default umask is set in /etc/profile; for setti
# for ssh logins, install and configure the libpam-um
#umask 022

# if running bash
if [ -n "$BASH_VERSION" ]; then
    # include .bashrc if it exists
    if [ -f "$HOME/.bashrc" ]; then
        . "$HOME/.bashrc"

[1]+  Stopped                 less -S .profile
user1@vm1:~$ less -S .bashrc
# for examples

# If not running interactively, don't do anything
[ -z "$PS1" ] && return

# don't put duplicate lines in the history. See bash(
# don't overwrite GNU Midnight Commander's setting of
HISTCONTROL=$HISTCONTROL${HISTCONTROL+:}ignoredups
# ... or force ignoredups and ignorespace
HISTCONTROL=ignoreboth

# append to the history file, don't overwrite it
shopt -s histappend

[2]+  Stopped                 less -S .bashrc
user1@vm1:~$ less -S .bash_history
echo Hello, $LOGNAME!
echo 'echo Hello, $LOGNAME!' >> .profile
cp .profile .profile.bak
tail .profile
ls -altr
history -w
ls -al
cat .profile
echo Hello, $LOGNAME!
echo 'echo Hello, $LOGNAME!' >> .profile
cp .profile .profile.bak
tail .profile
ls -altr

[3]+  Stopped                 less -S .bash_history
user1@vm1:~$ jobs
[1]   Stopped                 less -S .profile
[2]-  Stopped                 less -S .bashrc
[3]+  Stopped                 less -S .bash_history
user1@vm1:~$ fg
user1@vm1:~$ fg
user1@vm1:~$ fg
user1@vm1:~$ fg
-bash: fg: current: no such job
user1@vm1:~$ jobs
user1@vm1:~$           

  1. .profile

    來檢視。注意我如何使用

    -S

    參數,讓

    less

    開啟

    -chop-long-lines

    選項來啟動。
  2. 挂起

    less

  3. .bashrc

    來檢視。
  4. less

  5. .bash_history

  6. less

  7. 列印挂起程式的清單。
  8. 切換到

    less

  9. 退出它。
  10. 切換到第二個

    less

  11. 切換到第一個

    less

  12. 嘗試切換到最後一個程式。沒有任何程式,但你這樣做是為了確定确實沒有。
  13. 列印挂起程式的清單。這是為了確定沒有背景任務,通過看到

    jobs

    列印出空的輸出。

man bash

,搜尋 JOB CONTROL,輸入

/, JOB CONTROL, <ENTER>

,并閱讀它。

練習 10:Bash:程式退出代碼(傳回狀态)

Exercise 10. Bash: program exit code (return status)

讓我們假設你要複制一個目錄。你可以通過鍵入

cp -vR /old/dir/path /new/dir/path

來執行此操作。發出此指令後,你可能想知道如何進行。目錄是否被複制?還是出現了一些錯誤,因為目标目錄空間不足,或其他出現錯誤的東西?

為了了解它是如何工作的,你必須了解兩個程式如何通信。我們先這樣說,bash 隻是另一個程式,是以一般來說,當你發出上述的

cp

指令時,一個程式(bash,它是父程序)調用了另一個程式(

cp

,它是子程序)。

在 Linux 中,有一個标準機制,用于擷取從子程序到父程序的資訊,這個機制稱為

退出狀态或傳回代碼

。通過使用這種機制,當子程序完成其工作時,一個小的數字從子程序(或被調用者,這裡是

cp

)傳遞給父程序(或調用者,這裡是 bash)。當程式在執行期間沒遇到錯誤時,它傳回

,如果發生某些錯誤,則此代碼不為零。就是這麼簡單。Bash 中的這個退出代碼儲存到

?

環境變量,你現在知道了,可以使用

$?

來通路。

讓我再次重複一下我現在所說的話:

Bash 等待你的輸入
Bash 解析你的輸入
Bash 為你啟動程式,并等待這個程式退出
    程式啟動
    程式做你讓他做的事情
    程式生成了退出代碼
    程式退出并且将退出代碼傳回給 Bash
Bash 将這個退出代碼賦給變量 ?           

現在你學到了如何列印出你的程式的退出狀态。

1: ls
2: echo $?
3: ls /no/such/dir
4: echo $?           

user1@vm1:~$ ls
hello.txt  ls.out
user1@vm1:~$ echo $?
0
user1@vm1:~$ ls /no/such/dir
ls: cannot access /no/such/dir: No such file or directory
user1@vm1:~$ echo $?
2
user1@vm1:~$           

  • 列印出一個目錄,成功。
  • ls

    的退出代碼,它是 ,這意味着

    ls

    沒有遇到任何錯誤。
  • 嘗試列印出不存在的目錄,當然失敗。
  • 列印

    ls /no/such/dir

    的退出代碼,它确實是非零。

man ls

的退出代碼部分。

練習 11:總結

Exercise 11. Bash: wrapping up

現在你已經嘗試過,如何在 Linux 中使用 CLI 的感覺,下一步是打開你喜歡的文本編輯器,并為自己制作下表。搜尋那些你不知道的指令和符号的意思。警告!為了有效,你必須手動輸入此表。搜尋這些新的術語和指令。

現在你将學習如何研究某些東西。并記住,不要複制粘貼!

術語

含義
vim 正常模式
vim 指令模式
CLI
SHell
配置
檔案
檔案描述符
程序
程式
環境
環境變量
重定向
管道
文本流
标準輸入
标準輸出
标準錯誤
EOF
過濾
任務
前台任務
背景任務
退出代碼

vim

指令

vim

h

j

k

l

i

o

<ESCAPE>

x

dd

:wq

:q!

/

less

less

j

k

q

--ch

/

&

Bash 和 Bash 内建指令

echo

history

exit

pwd

=

$

?

set

env

export

$LANG

read

<CTRL>+z

<CTRL>+c

jobs

fg

>

<

>>

`

/dev/stdin

/dev/stdout

/dev/stderr

其它你學到的程式

man

ls

cat

dpkg-reconfigure

head

tail

grep

awk

sed

tee

dd

pv

locale

sudo

cp

mv

rm

touch

wc

填寫表格後,在後面為每個指令編寫注解,然後重複一次,然後再睡一個禮拜。是的,我的意思是,從那些筆和紙上抖掉灰塵,然後這樣做。

沒有附加題。隻需學習這些指令,直到你熟記于心。

繼續閱讀