1. 程式人生 > >linux下的IO重定向與管道相關的知識簡析

linux下的IO重定向與管道相關的知識簡析

io操作 簡單的 先來 兩個 整數 指令 cpu director 文件描述

一、bash重定向部分簡單翻譯

1.1、bash手冊關於重定向短短的註解(因為過於經典,所以摘錄出來)

我的翻譯要開始毀經典啦...

  • 參考:
    https://blog.csdn.net/spch2008/article/details/51433353/
    https://www.cnblogs.com/lqminn/archive/2013/05/30/3108283.html
    https://bash.cyberciti.biz/guide/Here_strings
    https://bbs.csdn.net/topics/390792870
    https://blog.csdn.net/ccwwff/article/details/48519119
    https://www.jb51.net/article/138686.htm
    https://chegva.com/archive/
    https://blog.csdn.net/ccwwff/article/details/48519119
    http://www.cnblogs.com/f-ck-need-u/p/7325378.html
    http://www.catonmat.net/blog/bash-one-liners-explained-part-three/
    https://github.com/pkrumins/bash-redirections-cheat-sheet

?

  • 重定向概述

技術分享圖片
? ? ? ?在shell接口執行命令前,命令的輸入與輸出可能會使用一個特別的標記符號來解釋它們的重定向。重定向也應用於在當前的shell環境下打開和關閉文件。重定向的標記符號是按照從左到右的出現的順序來處理的。

?
? ? ? ?請看這兩種形式的表示 {varname}> file , 2>file.很多情況,在重定向符號之前會有一個文件描述符(具體何為文件描述符,後面我們會簡單說明),這個描述符通常為一個數字,當然,也可能是一個由符號{}引用起來的一個字符串,這種情況下,除了>&-,<&-之外的重定向操作(>&-,<&-這兩個與關閉文件描述符相關的),shell會給{varname}分配一個大於10的數字(0-9這10個數字已經預先定義了,有其特定含義和用途),如果{varname}>&-,{varname}<&-是這兩種形式,表示關閉分配給{varname}的文件描述符。

?
? ? ? ?在下面的描述中,如果一個重定向符號前面的文件描述符編號省略了,而且重定向的第一個字符是"<",就表示這個重定向為標準輸入(文件描述符為0)。如果第一個字符是">",就表示這個重定向為標準輸出(文件描述符為1)。
?
? ? ? ?在下面關於文件重定向的操作的描述中,重定向運算後的單詞,如果沒有特殊說明,要進行bash的展開操作,大括號展開,波浪號展開,參數替換展開,命令替換展開,算術運算展開,引用去掉,路徑展開以及單詞拆分等。如果展開後有多個單詞,bash就會出錯。

小貼士
    這裏翻譯只是字面的翻譯,如果你對bash的展開特性(brace expansion,tilde expansion,parameter expansion,command substitution,arthmetic expansion,quote removal,pathname expansion,word splitting )不太了解,而又糾結這個東西,我建議你先去了解bash的expansion(展開)特性以及它們在shell執行命令的替換的先後順序。

?
    註意重定向符號出現的先後順序,它是至關重要的。例如下面的命令:

ls > dirlist 2>&1

    命令的意思大概是把指令ls的標準輸出和標準錯誤重定向到文件dirlist中。而我們稍微改變一下命令中重定向符號出現的先後順序,請看:

ls 2>&1 > dirlist

    ls加上後邊部分的東西的標準輸出被寫入文件dirlist中,因為在ls的標準輸出在被重定向到文件dirlist前,ls的標準錯誤輸出已經被復制到標準輸出中了。
?

? ? ?當bash使用重定向的時候,它會對下面的列出的幾個文件特殊處理:
? ? ?幾種形式如下:
? ? ?? ? ?1.** /dev/fd/fd**
? ? ?? ? ?2. **/dev/stdin**
? ? ?? ? ?3. **/dev/stdout**
? ? ?? ? ?4.**/dev/stderr**
? ? ?? ? ?5.** /dev/tcp/host/port**
? ? ?? ? ?6. **/dev/udp/host/port**

? ? ?小貼士:/dev/fd是一個目錄,設備文件(文件描述符相關的)。後邊的fd是一個整數,表示復制文件描述符fd;

        第一種情況:(/dev/fd/fd)

如果最後一個fd是一個整數,則復制文件描述符fd。請看服務器上默認的:
[root@vir-rs1 ~]# ls -l /dev/fd/[0-2]
lrwx------ 1 root root 64 Sep  8 16:40 /dev/fd/0 -> /dev/pts/0
lrwx------ 1 root root 64 Sep  8 16:40 /dev/fd/1 -> /dev/pts/0
lrwx------ 1 root root 64 Sep  8 16:40 /dev/fd/2 -> /dev/pts/0

        第二種情況:(/dev/stdin)

復制文件描述符0.服務器存儲文件路徑/dev/stdin (設計要遵循unix萬物皆文件的哲學思想,)
[root@vir-rs1 ~]# ls -l /dev/stdin
lrwxrwxrwx 1 root root 15 Sep  5 09:38 /dev/stdin -> /proc/self/fd/0

        第三種情況:(/dev/stdout)

復制文件描述符1(/dev/stdout).

        第四種情況:(/dev/stderr)

復制文件描述2(/dev/stderr)

        第五種情況:(/dev/tcp/host/port)

host表示主機名或者ip地址,port表示端口號或服務。如果host是一個有效的主機名或ip地址,port是一個整數端口號或服務名,bash會嘗試打開一個連接到該套接字的端口的TCP連接;

        第六種情況:(/dev/udp/host/port)

host表示主機名或者ip地址,port表示端口號或服務。如果host是一個有效的主機名或ip地址,port是一個整數端口號或服務名,bash會嘗試打開一個連接到該套接字的端口的UDP連接;

        小貼士:進程向內核申請註冊一個套接字,套接字需要用ip和端口號來標識。主機名最終會被解析成ip地址,進程註冊的套接字可能是提供某一種服務。

?
        如果不能打開文件或者不能創建文件會導致重定向失敗。

        重定向使用大於9的文件描述符應該小心謹慎,因為它們(大約9的文件描述符數字)可能會和shell的內部使用的文件描述符相沖突。

?

  • 輸入重定向
    技術分享圖片

[輸入重定向]
        輸入重定向會打開下面的word展開後所形成的文件名以備讀取,並將其作為文件描述符n,如果沒有指定n,則將word作為標準輸入,之前的標準輸入如果來自於鍵盤,現在省略了n,就表示word展開頭的結果作為標準輸入的內容。
標準格式為:

[n]<word 
例如cat < `echo /etc/crontab`,後邊的 `echo /etc/crontab`是bash的展開特性中的一種,
是command substitution(命令替換),替換後的結果作為cat程序的標準輸入。
cat發起一個進程,從後邊的命令替換後的結果中讀入內容,由CPU調度,加載到內存中。

  • 輸出重定向
    技術分享圖片

[輸出重定向]
        輸出重定向會打開word展開後的文件以備寫入,並將其作為文件描述符n。如果沒有指定n,則將其作為標準輸出(文件描述符為1)。如果文件不存在,會先創建這個文件。如果文件存在,就會將文件中原先的內容清空(通過文件偏移量指針來實現,具體可以參考unix高級環境變量編程)。

?

  • 追加輸出重定向
    技術分享圖片
    [追加輸出重定向]
            這種類型的輸出重定向,word展開後形成的文件,以備追加使用,並將其作為文件描述符n。如果沒有指定n,則將其作為標準輸出(文件描述符1)。同樣,文件不存在會創建,存在,則直接把文件偏移量指針指向文件的末尾,然後追加寫入內容。

?

  • 標準輸出和標準錯誤
    技術分享圖片
    [標準輸出重定向和標準錯誤重定向]
            這種結構允許把標準輸出(文件描述符為1)和標準錯誤輸出(文件描述為2)一起寫入word擴展後的文件中。有以下幾種形式可以表示:
&>word 
>&word
>word 2>&1

?

  • 追加標準輸出和標準錯誤
    技術分享圖片
    [追加標準輸出重定向和追加標準錯誤重定向]
            這種結構允許把標準輸出(文件描述符為1)和標準錯誤輸出(文件描述符為2)一起追加寫入word展開後的文件中。有以下兩種格式:
&>>word
>>word 2>&1

?

  • 此處創建文檔的使用(Here Documents)以及此處字符串(Here Strings)
    技術分享圖片
    [此處文檔]
            這種類型的重定向結構讓shell從當前文本源中讀取輸入,直到遇到其中一行只包含word(這個word是我們自己定義的,通常為EOF(end of file),這一行word的前面不能有空白字符)。所有被讀取的行都被當作命令的標準輸入。格式如下:
    <<[-]word
        here-document
    delimiter 

    #轉換一下就成了我們經常看見的一種格式:
    <<EOF
        documents
        ...
    EOF

        word這個標簽不會進行參數展開,命令替換,算術展開,文件名展開。簡單來說,標簽部分,你即使用到了一個表達式,比如1+2,那麽,你結束也要用1+2,即使有涉及展部分,也不會做bash的展開處理。
        還有兩種情況要說明:
        第一種就是,如果用單引號"‘"或者雙引號‘"‘,把word引用起來了,這種表示,我here-document的部分中如果即使有涉及 參數展開,命令替換,算術展開等都不會被bash展開處理。
        第二種就是,如果沒有用引號(雙引號和單引號都行)引起word,表示here-document中的部分內容會被bash的參數展開,命令替換,算術展開所匹配且會做對應的展開處理,不過換行符\n不會被處理,而且如果你要顯式特殊字符(\,$,`)的本身,要用字符"\"做轉移處理。簡單看看下面三個例子:

示例1: 這個表示我標簽word不會被bash展開處理的示例,$UID不會被替換成0或者其他用戶id編號(結果自己分析,我不貼來)
cat <<$UID
123
hello,world
$UID
`$HOSTNAME`
`pwd`
$date
\n
\\`
\$
$UID 

示例2:    這個表示我標簽word用引號引起來,看看here-documents部分的解析情況(結果自己分析,我不貼來)
cat <<"EOF"
123
hello,world
$UID
`$HOSTNAME`
`pwd`
$date
\n
\\`
\$
EOF

示例3:這個表示我標簽word沒有用引號引起來,看看here-documents部分的解析情況(結果自己分析,我不貼來)
cat <<EOF
123
hello,world
$UID
`$HOSTNAME`
`pwd`
$date
\n
\\`
\$
EOF 

[此處字符串(即插即用字符串)]
? ? ?Here Strings這種形式是此處文檔的一種變種,形式如下:

<<<word

? ? ?word支持bash的展開特性,然後作為命令的標準輸入。例子:

wc -w <<< `date`
wc -w <<< "this is a strings"

?

技術分享圖片

  • 對文件描述符的操作(復制,移動,讀寫)
            操作文件描述符,打開分為三種,形式如下:
[n]<&word和[n]>&word屬於一類;(復制文件描述符)
[n]<&digit-和 [n]>&digit-屬於一類;(移動關閉文件描述符)
[n]<>word 自成一類;(讀寫文件描述符)



[復制文件描述符]
格式1 : [n]<&word #為了好理解,寫出幾個符合這種格式的,2<&1,<&,<&2,1<&-等

        這種格式,表示用於復制輸入文件描述符。如果word做展開後的結果包含一個或多個數字,則有n表示的文件描述符是後邊數字表示的文件描述符的復制,如果word展開後數字代表的文件描述符不能作為打開一個輸入的文件的文件描述符,沖頂會就會報錯。簡單來說,word如果是一個數字,如果這個數字對應的文件不能作為輸入流,就是裏面的東西讀不到,就會報錯。如果word展開後的結果值是一個符號"-",表示文件描述符n將會被關閉。如果n生路了,表示使用標準輸入(文件描述符為0)。

格式2:[n]>&word
        這種格式,表示復制輸出文件描述符。如果省略n表示,標準輸出(文件描述符1)。如果word做展開後對應是一個數字且這個數字對應的文件描述符指定的文件不能被輸出,簡單來說數據流不能往這個文件中寫,重定向就會報錯。如果n生路了,且word展開後並不是一個數字, >& tring,表示標準輸出和標準錯誤都被重定向到後邊的文件中。之前有講過這種情況。

[移動文件描述符]
格式1:[n]<&digit-
        移動digit這個數字指定的文件描述符內容到文件描述符n或者移動到標準輸入(文件描述符0,n省略的情況下)。digit指定的文件描述符將會被關閉。

格式2:[n]>&digit-
        移動digit這個數字指定的文件描述符內容到文件描述符n或移動到標準輸出(文件描述符1,n省略的情況下)。digit指定的文件描述符將會被關閉。


[打開文件描述符並讀和寫]
        以讀寫的方式打開word展開後的文件,並將文件描述符n重定向到該文件,如果n不指定,表示標準輸入。

?

1.2、文件描述符和流的概念

本小結摘自《Unix高級環境變量編程》第三版的第三章的3.2小結部分以及第五章節的5.2小結部分

        通過以前的博客,我們有了基本文件系統的分層以及文件的認知。說到文件,我們不得不提一下一個叫做文件描述符的東西。對於操作系統內核而言,所有打開的文件都通過文件描述符來引用。文件描述符是一個非負整數。當打開一個現有文件或創建一個新文件時,內核向進程返回一個文件描述符。按照慣例,Unix系統shell把文件描述符0與進程的標準輸入關聯,文件描述符1與標準輸出關聯,文件描述符2與標準錯誤關聯。

        我們也之前講過,操作系統抽象出了兩層功能,一個是底層的系統調用接口,另外一個是叫做庫(函數)的東西,而在我們的庫函數中,有一類叫做標準I/O庫的東西,這個庫是由ISO C標準進行解釋說明的。所有的I/O函數都是圍繞文件描述符的。當打開一個文件時,即返回一個文件描述符,然後該文件描述符就用於後續的I/O操作。而對於標準I/O庫,它們的操作是圍繞流(stream)進行的。當用標準的I/O庫打開或創建一個文件時,我們已經使一個流與一個文件相關聯。


1.3、IO重定向概念

?        在第一小節,我花了過多的篇幅去翻譯man手冊中關於linux 的bash重定向部分,我是為了給自己做一個標記,方便下次自己學習查閱使用。如果沒有心思或時間,可以直接跳過第一小節的部分,因為我是從這一部分正式介紹這篇博文的主題。
        首先來思考一個問題,何為IO?我們之前的博文中已經大體介紹過計算機的五大基本部件,分別是運算器控制器存儲器輸入設備輸出設備,其中運算器和控制器集成於CPU這個硬件中來,存儲器主要以內存為主,包括持久性存儲設備硬盤,輸入設備主要是鍵盤,鼠標,掃描器等,輸出設備主要是顯示器,打印機。這五大基本部件,是為了更好的讓計算機來完成某些工作,這些工作的過程中就涉及到這些設備的IO操作。
        抽象概念,程序是由指令+數據組成。指令通常由程序來提供,但是數據卻不單單如此。大部分的程序中的數據,可以有多種來源,程序內部自己賦值變量以及通過文件來提供這部分數據。程序本身也得有輸入和輸出(IO,Input,Output),這不過這部分被操作系統底層功能更好的給簡化來。

        可用的輸入設備:文件(unix抽象來萬物皆文件的概念),這個文件可以包括鍵盤設備、文件系統上的常規文件、網卡等;
        可用的輸出設備:文件,這個文件可以包括顯示器、文件系統上的常規文件、網卡等;

        程序的數據流有三種:輸入數據流(不顯式指明的輸入流叫標準輸入,字符簡寫為stdin,默認的標準輸入設備為鍵盤);輸出數據流(不顯式指定指明的時候為標準輸出,字符簡寫為stdout,默認的標準輸出設備為顯示器);錯誤輸出數據流(不顯示指定指明的時候為標準錯誤,字符簡寫為stderr,默認的標準錯誤輸出設備也是顯式器),如下表所示:

索引編號 名字 英文簡寫 默認的設備
1 標準輸入 stdin 鍵盤
2 標準輸出 stdout 顯式器
3 標準錯誤 stderr 顯示器


        文件描述符(fd,file descriptor)-unix的概念相當於windows的文件句柄;人更傾向於識別的是名字,計算機識別的是二進制,為了更好的讓系統完成工作,操作系統,把文件系統的文件以及其他抽象出來的文件都用不同標識的數字來表示,這些數字有一個統稱的概念,叫做文件描述符。下表針對上述提到的三種標準流給出默認的標準流的文件描述符:

索引編號 名字 英文簡寫 文件描述符
1 標準輸入 stdin 0
2 標準輸出 stdout 1
3 標準錯誤 stderr 2

?


基礎IO重定向(不含有復雜的)如下表所示:

索引編號 重定向名字 符號表示 說明 特點
1 輸出重定向 > set -C ,會導致覆蓋內容失敗(前提是文件已存在才會失敗),set +C關閉。 覆蓋寫入 ,不存在文件先創建再寫入
2 強制輸出重定向 > set -C的時候也會強制寫入,危險 覆蓋寫入,不存在文件先創建再寫入
3 追加輸出重定向 >> 追加寫入,不會覆蓋原有文件內容,不存在文件先創建然後再寫入
4 錯誤輸出重定向 2> 覆蓋寫入錯誤信息,文件不存在先創建文件,然後再寫入錯誤信息
5 追加錯誤輸出重定向 2>> 追加寫入錯誤信息,文件不存在先創建,然後再寫入錯誤信息
6 合並輸出流和錯誤輸出流,覆蓋 &>或>&或COMMAND> FILE 2>&1 set -C,會導致覆蓋內容寫入失敗(前提是文件已存在才會失敗),set +C關閉 把標準輸出和標準錯誤信息都追加寫入文件中
7 合並輸出流和錯誤輸出流,強制覆蓋 COMMAND> FILE 2>&1 set -C的時候回強制寫入,及時文件存在,危險 把標準輸出和標準錯誤信息都強制寫入文件中,文件存在也會強制寫入,不管安全選項。文件不存在會先創建
8 合並輸出流和錯誤輸出流,追加 &>>或COMMAND>> FILE 2>&1 把標準輸出和標準錯誤信息都覆蓋寫入文件中,文件不存在會先創建然後覆蓋寫入
9 輸入重定向 <
10 此處創建文檔(Here Documents) < <


?

網上的一個總結,覺得比較好,列出來:

 I/O REDIRECTORS
    cmd1 | cmd2   use stdout of cmd1 as stdin for cmd2,cmd1的標準輸出作為cmd2的標準輸入;
         > file   save stdout to file ,把標準輸出重定向到file文件;
         < file   use file as stdin ,把file當做標準輸入重定向;
        >> file   append stdout to file (create file if nonexistent) ,追加標準輸出重定向到file(如果file不存在,先創建);
        >| file   force stdout to file (even if noclobber is set) 強制把標準輸出重定向到file(即使noclobber啟動的);
       n>| file   force stdout to file from descriptor n (even...) 把文件描述符n的標準輸出強制定向到file;
        <> file   use file as both stdin and stdout (process in place) ,把文件當做標準輸入和標準輸出;
       n<> file   use as both stdin and stdout for file descriptor n
        << label  use here-document (within scripts specifies batch input) 此處文檔;
        n> file   direct file descriptor n to file  把文件描述符n給文件file;
        n< file   take file descriptor n from file 把文件描述符n作為輸入;
       n>> file   append descriptor n to file (or create file for n) 追加文件描述符n到文件;
       n>&        duplicate stdout to file descriptor n  ,復制標準輸出給文件描述符n;
       n<&        duplicate stdin from file descriptor n  從文件描述符n中復制標準輸入;
       n>&m       make file descriptor n a copy of the stdout fd 從標準輸出文件描述符中復制一份副本到文件描述符n;
       n<&m       make file descriptor n a copy of the stdin  fd,從標準輸入文件描述符中復制一份副本到文件描述符n;
        >&file    send stdout and stderror to file 把標準輸出和標準錯誤都發送給文件file;
       <&-        close stdin  ,關閉標準輸入;
       >&-        close stdout,關閉標準輸出;
      n<&-        close input  from file descriptor n,關閉來自於文件描述符n的輸入;
      n>&-        close output from file descriptor n,關閉來自於文件描述符n的輸出;

學習必看的重定向相關的文章


官方的一張圖:
技術分享圖片

  • 管道
    管道主要是連接程序,實現將前一個命令的輸出直接定向後一個程序當作輸入數據流。大體格式如下:
COMMAND1 | COMMAND2 | COMMAND3 | ...

1.4、幾個涉及重定向相關的命令

  • cat 命令
我們之前針對cat命令的選項進行過基本說明,我們這裏直接引用;
Concatenate FILE(s), or standard input, to standard output.
cat的功能就是把多個文件的內容進行合並並輸出到標準輸出(默認顯示器),其實當我們省略
cat的FILES的時候,cat是能夠從標準輸入(默認鍵盤)接收內容的。

結構:
cat [OPTION]... [FILE]...

我們來看幾個簡單的案例。

1. 案例1:直接從默認標準輸入中接受內容並寫入默認標準輸出
[root@ACA86E6E ~]# cat
xiaochang,nihaoma?
xiaochang,nihaoma?
123
123
^C

2. 案例2:從文件中輸入內容給cat並顯示到默認標準輸出,這樣通過重定向改變來cat程序的輸入流
[root@ACA86E6E ~]# cat < /etc/fstab

#
# /etc/fstab
# Created by anaconda on Tue Aug  7 21:36:15 2018
#
# Accessible filesystems, by reference, are maintained under ‘/dev/disk‘
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info
#
UUID=122ecc46-d280-4282-a800-d6e112227b60 /                       xfs     defaults        0 0
UUID=fd2db043-73ce-4418-bf16-93ffaceb7a6f /boot                   xfs     defaults        0 0
UUID=79f6ca5b-2ef2-43cc-b100-d63555b1b547 swap                    swap    defaults        0 0
  • tr命令:
tr - translate or delete characters,翻譯或刪除字符

tr [OPTION]... SET1 [SET2]

把輸入的數據中的字符,凡是在SET1定義範圍內出現的,都對位轉換為SET2出現的字符。
默認不直接操作源文件,如果輸入的流來自原某個具體文件系統的文件的話。

案例一:把小寫字母轉大寫字母
[root@localhost ~]# echo {a..z}|tr [[:lower:]] [[:upper:]]
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
[root@localhost ~]# echo {a..z}|tr [a-z] [A-Z]
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

[root@localhost ~]# tr [[:lower:]] [[:upper:]] < /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
ADM:X:3:4:ADM:/VAR/ADM:/SBIN/NOLOGIN
LP:X:4:7:LP:/VAR/SPOOL/LPD:/SBIN/NOLOGIN
....

案例二:修剪字符串
[root@localhost ~]# echo {a..z}|tr -d [a-j]
          k l m n o p q r s t u v w x y z
[root@localhost ~]#                 
  • tee命令:
tee - read from standard input and write to standard output and files
從標準輸入從讀取數據,並把其寫入標準輸出和文件中。
結構:
tee [OPTION]... [FILE]...

選項:
-a,--append:追加內容到給定的FILEs中,不覆蓋寫;

案例:
[root@localhost ~]# head -4 /etc/passwd | tr [[:lower:]] [[:upper:]] | tee -a /tmp/test/issue 
ROOT:X:0:0:ROOT:/ROOT:/BIN/BASH
BIN:X:1:1:BIN:/BIN:/SBIN/NOLOGIN
DAEMON:X:2:2:DAEMON:/SBIN:/SBIN/NOLOGIN
ADM:X:3:4:ADM:/VAR/ADM:/SBIN/NOLOGIN
[root@localhost ~]# cat /tmp/test/issue
\S
Kernel \r on an \m

ROOT:X:0:0:ROOT:/ROOT:/BIN/BASH
BIN:X:1:1:BIN:/BIN:/SBIN/NOLOGIN
DAEMON:X:2:2:DAEMON:/SBIN:/SBIN/NOLOGIN
ADM:X:3:4:ADM:/VAR/ADM:/SBIN/NOLOGIN

1.5、實際案例說明

  • 改變默認標準輸入流
利用輸入重定向符號"<"來讀取/etc/passwd文件的最後2行給cat程序:
[root@localhost ~]# tail -2 /etc/passwd | cat
gentoo:x:4001:4001::/var/tmp/gentoo:/bin/csh
fedora:x:4002:4002:Fedora Core:/home/fedora:/bin/tcsh
分析:管道把前邊部分的輸出流給管道後邊部分的輸入流,我們就說tail -2 /etc/passwd這一部分
的程序的標準輸出重定向到了管道後邊的程序,cat的標準輸入被重定向到流管道前邊的
部分(即cat程序的標準輸入來自於管道前邊部分的內容)。
  • 改變默認標準輸出輸出流
把ls 查看/etc/pam.d目錄下的長格式顯示的內容寫入到/var/tmp目錄下的一個叫test(文件默認不存在)的文件中
[root@localhost ~]# ls -l /etc/pam.d/ > /var/tmp/test
[root@localhost ~]# ls -l /etc/pam.d/ 1> /var/tmp/test
[root@localhost ~]# cat /var/tmp/test
total 100
-rw-r--r--. 1 root root 192 Mar  6  2015 chfn
-rw-r--r--. 1 root root 192 Mar  6  2015 chsh
-rw-r--r--. 1 root root 232 Mar  6  2015 config-util
-rw-r--r--. 1 root root 293 Jul 30  2014 crond
... #此處省略

分析:
利用輸出重定向符號">"來改變程序ls 到默認標準輸出的流到文件/var/tmp/test中。因為標準
輸出文件描述符為1,省略或者顯式指定(即>或1>)都可以。

啟用noclobber,然後再次測試覆蓋寫:
set -o noclobber或set -C 表示啟用noclobber特性;(當前shell級別)
set +o noclobber 或set +C 表示關閉noclobber特性;(當前shell級別)
[root@localhost ~]# ls /var/tmp/test
/var/tmp/test
[root@localhost ~]# cat /var/tmp/test
ls: cannot access hello: No such file or directory
-rw-------. 1 root root 263 Sep  5 06:12 .lesshst
[root@localhost ~]# echo 123 > /var/tmp/test
[root@localhost ~]# cat /var/tmp/test
123
[root@localhost ~]# set -o noclobber
[root@localhost ~]# echo 145 > /var/tmp/test
-bash: /var/tmp/test: cannot overwrite existing file
[root@localhost ~]# ls -l /var/tmp/test10
ls: cannot access /var/tmp/test10: No such file or directory
[root@localhost ~]# echo 145 > /var/tmp/test10
[root@localhost ~]# cat /var/tmp/test10
145
[root@localhost ~]# echo hello > /var/tmp/test10
-bash: /var/tmp/test10: cannot overwrite existing file
[root@localhost ~]# echo hello >| /var/tmp/test10
[root@localhost ~]# cat /var/tmp/test10
hello
首先/var/tmp/test文件存在,我在安全選項未開啟前,直接覆蓋,可以寫入。
開啟選項後,直接覆蓋寫入會報錯,而我寫入另外一個不存在的文件/var/tmp/test10
就可以寫入,然後我利用強制覆蓋寫(>|)可以寫入。
  • 改變默認標準錯誤流
[root@localhost ~]# ls -l hello
ls: cannot access hello: No such file or directory
[root@localhost ~]# ls -l hello .lesshst 2>/var/tmp/test 
-rw-------. 1 root root 263 Sep  5 06:12 .lesshst
[root@localhost ~]# cat /var/tmp/test 
ls: cannot access hello: No such file or directory

分析:因為默認輸出重定向符號">"前邊部分的文件描述符數字如果省略表示標準輸出,
如果改變標準錯誤的流的重定向,需要顯式指明文件描述符 2。
  • 把標準錯誤流和標準輸出流都改變
[root@localhost ~]# ls -l hello .lesshst &>/var/tmp/test 
[root@localhost ~]# cat /var/tmp/test
ls: cannot access hello: No such file or directory
-rw-------. 1 root root 263 Sep  5 06:12 .lesshst
[root@localhost ~]# ls -l hello .lesshst >& /var/tmp/test1
[root@localhost ~]# cat /var/tmp/test1
ls: cannot access hello: No such file or directory
-rw-------. 1 root root 263 Sep  5 06:12 .lesshst
[root@localhost ~]# ls -l hello .lesshst > /var/tmp/test2 2>&1
[root@localhost ~]# cat /var/tmp/test2 
ls: cannot access hello: No such file or directory
-rw-------. 1 root root 263 Sep  5 06:12 .lesshst
三種形式,把標準錯誤和標準輸出的流都覆蓋寫入某文件。如下格式:
COMMAND &> /PATH/TO/SOMEDIR/FILE
COMMAND >& /PATH/TO/SOMEDIR/FILE
COMMAND > /PATH/TO/SOMEDIR/FILE 2>&1
  • 自定義文件描述符來完成流定向工作
[root@localhost ~]# cat <<EOF>/root/test.log
> hello,world
> i am yanhui
> nice to meet you
> nice to meet you too
> ok,bye.
> EOF
[root@localhost ~]# exec 6</root/test.log 
[root@localhost ~]# cat <&6
hello,world
i am yanhui
nice to meet you
nice to meet you too
ok,bye.
[root@localhost ~]# ls -l /dev/fd/6
lr-x------. 1 root root 64 Sep  9 05:43 /dev/fd/6 -> /root/test.log
[root@localhost ~]# exec 6<&-
[root@localhost ~]# ls -l /dev/fd/6
ls: cannot access /dev/fd/6: No such file or directory

自定義的fd(file descriptor)的數字範圍0~9,0,1,2默認都是占用的,使用3到9的時候要註意是否占用。可以查看/dev/fd目錄:
ls -l /dev/fd/

上面的示例,我定義了一個文件描述符6,把它與/root/test.log文件關聯,這樣,可以使用
文件描述符6的重定向的輸入流,來自於/root/test.log,非默認標準輸入流,使用的時候,
記得要帶上文件描述符6,最後我們關閉流自定義的文件描述符。

[root@localhost ~]# exec 7>/root/yanhui.log
[root@localhost ~]# cat /root/yanhui.log
[root@localhost ~]# echo ‘123‘
123
[root@localhost ~]# echo ‘123‘ >&7
[root@localhost ~]# echo ‘234‘ >&7
[root@localhost ~]# ls -l /dev/fd/7
l-wx------. 1 root root 64 Sep  9 05:50 /dev/fd/7 -> /root/yanhui.log
[root@localhost ~]# exec 7>&-
[root@localhost ~]# ls -l /dev/fd/7
ls: cannot access /dev/fd/7: No such file or directory
[root@localhost ~]# cat /root/yanhui.log 
123
234

上面的例子中,我自定義一個文件描述符7,把該文件描述符的輸出流重定向到文件/root/yanhui.log中,
值得註意的時,我們自定義的文件描述符,流數據,不是覆蓋寫,而是追加寫。

小貼士:

自定義文件描述符,理解起來可能會有些難,如果新手入門,可以暫時忽略掉這一部分。
目前我工作中用到自定義文件描述符有兩個應用用途:
檢查ssh遠程登錄公鑰文件的一致性;
利用自定義文件描述符來結合mysqldump邏輯備份,實現批量任務備份。
  • 此處文檔(Here Documents)
結構形式三種:(以cat程序為例,EOF標簽和$UID標簽為例)
第一種===>標簽為特殊字符,驗證其不做bash的擴展(expansion)
標準格式:
<<[-]word
here-documents
...
delimiter
=============================
cat <<$UID
123
hello,world
0
$UID
============================
[root@localhost ~]# cat <<$UID
> 123
> hello,world
> 0
> $UID
123
hello,world
0

第二種===>驗證標簽(定界符匹配串)引起來
標準格式:
<<[-]["|‘]word["|‘]
here-documents
...
delimiter

[root@localhost ~]# cat <<-"EOF"
hello,world
\$
$date
\`
\`date`
how old are you?
EOF

hello,world
\$
$date
\`
\`date`
how old are you?

當我用引號(單引號或雙引號)把標簽(EOF)引起來的時候,中間Here-Documents部分的內容都沒有
進行bash的expansion,而且定界符結束匹配的是標簽沒有引號的部分即EOF。

第三種===>驗證標簽(定界符匹配串)沒引起來
[root@localhost ~]# cat <<-EOF
hello,world
\$
$date
\`
\`date`
how old are you?
EOF

hello,world
$

`
Sun Sep  9 05:16:37 CST 2018
how old are you?

可以看到,沒有引起(單引號或雙引號都可以做引用)標簽部分,特殊字符會被解析(準確點說叫做
會做部分類型的bash的擴展,expansion)
  • 此處文檔的變體(Here Strings)
標準格式:
<<<word
其中word部分支持bash的expansion;

[root@localhost ~]# cat<<<$HOSTNAME
localhost.localdomain
[root@localhost ~]# echo $HOSTNAME
localhost.localdomain

用cat程序來查看主機名,通過環境變量。

linux下的IO重定向與管道相關的知識簡析