徹底搞懂shell的高級I/O重定向

来源:https://www.cnblogs.com/f-ck-need-u/archive/2018/04/06/8727401.html
-Advertisement-
Play Games

本文目錄: 1.1 文件描述符(file description,fd) 1.2 文件描述符的複製 1.3 重定向順序很重要:">file 2>&1"和"2>&1 >file" 1.4 改變當前shell環境的重定向目標 1.5 關閉文件描述符 1.6 打開文件 1.7 文件描述符的移動 1.8 經 ...


本文目錄:
1.1 文件描述符(file description,fd)
1.2 文件描述符的複製
1.3 重定向順序很重要:">file 2>&1"和"2>&1 >file"
1.4 改變當前shell環境的重定向目標
1.5 關閉文件描述符
1.6 打開文件
1.7 文件描述符的移動
1.8 經典示例


基本的重定向功能想必都理解。本文對shell環境下的IO重定向稍作深入,相信看完後,能夠徹底理解 >file 2>&1 。

1.1 文件描述符(file description,fd)

文件描述符是IO重定向中的重要概念。文件描述符使用數字表示,它指明瞭數據的流向特征。

軟體設計認為,程式應該有一個數據來源、數據出口和報告錯誤的地方。在Linux系統中,它們分別使用描述符0、1、2來表示,這3個描述符預設的目標文件(設備)分別是/dev/stdin、/dev/stdout、/dev/stderr,它們分別是各個終端字元設備的軟鏈接。

[root@mariadb ~]# ll /dev/std*
lrwxrwxrwx 1 root root 15 Apr  2 07:57 /dev/stderr -> /proc/self/fd/2
lrwxrwxrwx 1 root root 15 Apr  2 07:57 /dev/stdin -> /proc/self/fd/0
lrwxrwxrwx 1 root root 15 Apr  2 07:57 /dev/stdout -> /proc/self/fd/1

[root@mariadb ~]# ll /proc/self/fd/
total 0
lrwx------ 1 root root 64 Apr  6 03:53 0 -> /dev/pts/2
lrwx------ 1 root root 64 Apr  6 03:53 1 -> /dev/pts/2
lrwx------ 1 root root 64 Apr  6 03:53 2 -> /dev/pts/2
lr-x------ 1 root root 64 Apr  6 03:53 3 -> /proc/14038/fd

在Linux中,每一個進程打開時都會自動獲取3個文件描述符0、1和2,分別表示標準輸入、標準輸出、和標準錯誤,如果要打開其他文件,則文件描述符必須從3開始標識。對於我們人為要打開的描述符,建議使用9以內的描述符,超過9的描述符可能已經被系統內部分配給其他進程。

文件描述符說白了就是系統為了跟蹤這個打開的文件而分配給它的一個數字,這個數字和文件綁定在一起,數據流入描述符的時候也表示流入文件。

而Linux中萬物皆文件,這些文件都可以分配描述符,包括套接字。

程式在打開文件描述符的時候,有三種可能的行為:從描述符中讀、向描述符中寫、可讀也可寫。從lsof的FD列可以看出程式打開這個文件是為了從中讀數據,還是向其中寫數據,亦或是既讀又寫。例如,tail命令監控文件時,就是打開文件從中讀數據的(3r的r是read,w是write,u是read and write)。

[root@mariadb ~]# lsof -n | grep "/a.sh" | column -t                 
tail  13563  root  3r  REG  8,2  182  69632966  /root/a.sh

1.2 文件描述符的複製(duplicate)

文件描述符的複製表示覆制文件描述符到另一個文件描述符中以作副本文件。使用"&"進行複製。如果不好理解複製的意思,將其理解為"綁定"、"重用"。

[n]<&word :將文件描述符n綁定到word 代表的文件或描述符。可以理解為文件描述符n重用word代表的文件或描述符。n不指定則預設為0(標準輸入就是0),表示標準輸入也將輸入到word所代表的文件或描述符中。

[n]>&word :將文件描述符n綁定到word 代表的文件或描述符。可以理解為文件描述符n重用word代表的文件或描述符。n不指定則預設為1(標準輸出就是1),表示標準輸出也將輸出到word所代表的文件或描述符中。

註意,每綁定一次,都會立刻重定向到對應的文件。請結合下麵的例子感受。

例如, 3>&1 表示fd=3綁定到fd=1上,而fd=1目前的重定向目標文件是/dev/stdout,因此fd=3也重定向到/dev/stdout,以後進程將數據寫入fd=3的時候,將直接輸出到屏幕。如果用"複製"來理解,就是fd=3是當前fd=1的一個副本,即指向/dev/stdout設備。如果後面改變了fd=1的輸出目標(如file1),由於fd=3的目標仍然是/dev/stdout,所以可以拿fd=3來還原fd=1使其目標變回/dev/stdout。

再例如, cat <&1 表示fd=0綁定到fd=1上,而此時fd=1的重定向文件是/dev/stdout,因此此時/dev/stdout既是標準輸入設備,也是標準輸出設備,也就是說進程從/dev/stdout(屏幕)接受輸入,輸入後再直接輸出到/dev/stdout。以下是結果:

[root@mariadb ~]# cat <&1
q   # 進入互動式,輸入數據
q   # 直接輸出

1.3 重定向順序很重要:">file 2>&1"和"2>&1 >file"

想必很多人都知道 >file 2>&1 的作用,它等價於 &>file ,表示標準輸出和標準錯誤都重定向到file中。那它和 2>&1 >file 有什麼區別呢?

首先解釋 >file 2>&1 。這裡分兩個過程:先打開file,再將fd=1重定向到file文件上,這樣file文件就成了標準輸出的輸出目標;之後再將fd=2綁定(註意,是綁定不是重定向)到fd=1上,而fd=1此時已經重定向到file文件上,因此fd=2也重定向到file上。所以,最終的結果是標準輸出重定向到file上,標準錯誤也重定向到file上。

再解釋 2>&1 >file 。這裡也分兩個過程:先將fd=2綁定到fd=1上,而此時fd=1重定向的文件是預設的/dev/stdout,所以fd=2也重定向到/dev/stdout;之後再將fd=1重定向到file文件上(註意,不是綁定是重定向)。也就是說,這裡的標準錯誤和標準輸出仍然是分開輸出的,只不過是使用/dev/stdout替代了/dev/stderr,使用file替代了/dev/stdout。所以,最終的結果是標準錯誤輸出到/dev/stdout,即屏幕上,而標準輸出將輸出到file文件中。

可以使用下麵的命令來測試 2>&1 >file 。第一個ls命令是正確的,結果輸出到/tmp/a.log中,第二個ls命令是錯誤的,結果將直接輸出到屏幕上。

[root@mariadb ~]# ls /boot 2>&1 >/tmp/a.log
[root@mariadb ~]# ls sjdfk 2>&1 >/tmp/a.log
ls: cannot access sjdfk: No such file or directory

最後,也許你已經發現了,綁定和重定向是不同的,綁定不應該稱為重定向區分這兩個概念,在實際應用的過程中能解決非常多的疑惑。在本文結尾的最後一個例子中,你將能非常明確地體會到綁定和重定向的區別。

1.4 改變當前shell環境的重定向目標

如果在命令中直接改變重定向的位置,那麼命令執行結束的時候描述符會自動還原。正如上面的 ls /boot 2>&1 >/tmp/a.log 命令,在ls執行結束後,fd=2還原回預設的/dev/stderr,fd=1還原回預設的/dev/stdout。

但是我們可以通過exec程式直接在當前的shell環境下改變重定向目標,只有在當前shell退出的時候才會釋放描述符的綁定。

例如:下麵的命令將標準錯誤fd=2重定向到fd=3對應的文件上。

exec 2>&3

因此,我們可能在一段程式執行結束後,需要將描述符還原到原來的位置,並關閉不再需要的描述符。畢竟描述符也是資源,是有限的(ulimit -n)。

1.5 關閉文件描述符

[n]>&-
[n]<&-

關閉文件描述符的方式是將 [n]>&word 和 [n]<&word 中的word使用符號"-",這表示釋放fd=n描述符,且關閉其指向的文件。

1.6 打開文件

 [n]<> filename :打開filename,並指定其文件描述符為n,該描述符是可讀、可寫的描述符。若不指定n則預設為0,若filename文件不存在,則先創建filename文件。

例如:

[root@mariadb ~]# exec 3<> /tmp/a.log
[root@mariadb ~]# lsof -n | grep "/a.log" | column -t 
bash  13637  root  3u  REG  8,2  292018  69632965  /tmp/a.log

如果再 exec 1>&3 將fd=0綁定到fd=3上,那麼/tmp/a.log就成了標準輸入的來源。

1.7 文件描述符的移動

文件描述符的移動表示將文件描述符1移動到描述符2上,同時關閉文件描述符1。

 [n]>&digit- :將文件描述符digit代表的輸出文件移動到n上,並關閉digit值的描述符。

 [n]<&digit- :將文件描述符digit代表的輸入文件移動到n上,並關閉digit值的描述符。

例如:

[root@mariadb ~]# exec 3<> /tmp/a.log
[root@mariadb ~]# lsof -n | grep "/a.log" | column -t 
bash  13637  root  3u  REG  8,2  292018  69632965  /tmp/a.log
[root@mariadb ~]# exec 1>&3-  # 將3移動到1上,關閉3
[root@mariadb ~]# lsof -n | grep "/a.log" | column -t   # 在另一個bash視窗查看
bash  13637  root  1u  REG  8,2  292018  69632965  /tmp/a.log

可見,fd=3移動到fd=1後,原本與fd=3關聯的/tmp/a.log已經關聯到fd=1上。

1.8 經典示例

(1). 示例一:

以下是《Advanced Bash-Scripting Guide》中的示例:

echo 1234567890 > File # (1).寫字元串到"File".
exec 3<> File          # (2).打開"File"並且給它分配fd 3.
read -n 4 <&3          # (3).只讀4 個字元.
echo -n . >&3          # (4).寫一個小數點.
exec 3>&-              # (5).關閉fd 3.
cat File               # (6).1234.67890

(1)向文件File中寫入幾個字元。

(2)打開文件File以備read/write,並分配fd=3給該文件。

(3)將fd=0綁定到fd=3上,而fd=3的重定向目標為File,所以fd=0的目標也是File,即從File中讀取數據。這裡讀取4個字元,由於read命令中沒有指定變數,因此分配給預設變數REPLY。註意,這個命令執行結束後,fd=0的重定向目標會變回/dev/stdin。

(4)將fd=1綁定到fd=3上,而fd=3的重定向目標文件為File,所以fd=1的目標也是File,即數據寫入到File中。這裡寫入一個小數點。註意,這個命令結束後,fd=1的重定向目標回變回/dev/stdout。

(5)關閉fd=3,這也會關閉其指向的文件File。

(6)File文件中已經寫入了一個小數點。如果此時執行 echo $REPLY ,將輸出"1234"。

(2). 示例二:關於描述符恢復、關閉

exec 6>&1                   # (1)
exec > /tmp/file.txt        # (2)
echo "---------------"      # (3)
exec 1>&6 6>&-              # (4)
echo "==============="      # (5)

(1)首先將fd=6綁定到fd=1,此時fd=1的重定向目標為/dev/stdout,因此fd=6的重定向目標為/dev/stdout。

(2)將fd=1重定向到/tmp/file.txt文件。此後所有標準輸出都將寫入到/tmp/file.txt中。

(3)寫入數據。該數據將寫入到/tmp/file.txt中。

(4)將fd=1綁定回fd=6,此時fd=6的重定向目標為/dev/stdout,因此fd=1將恢復到/dev/stdout上。最後將fd=6關閉。

(5)寫入數據,這段數據將輸出在屏幕上。

可能你會疑惑,為什麼要先將fd=1綁定到fd=6上,再用fd=6來恢復fd=1,恢復的時候直接將fd=1重定向回/dev/stdout不就可以了嗎?

實際上,這裡借用fd=6這個中轉描述符是為了方便操作。你可以不它,但是再恢復fd=1的重定向目標的時候,應該重定向到/dev/{偽終端字元設備}上,而不是/dev/stdout,因為/dev/stdout是軟鏈接,其目標指向/proc/self/fd/1,但該文件還是軟鏈接,它指向/dev/{偽終端字元設備}。同理/dev/stdin和/dev/stderr都一樣。

因此,如果你當前所在的終端如果是pts/2,那麼可以使用下麵的命令來實現上面同樣的功能:

exec > /tmp/file.txt
echo "---------------"
exec >/dev/pts/2
echo "==============="

如果不借用fd=6這個中轉描述符,你要先去獲取並記住當前shell所在的終端,很不方便。而且,如果要恢復的不是fd={0,1,2},那就更麻煩。

最後給張描述符複製、恢復的過程實例圖:

 

回到Linux系列文章大綱:http://www.cnblogs.com/f-ck-need-u/p/7048359.html
回到網站架構系列文章大綱:http://www.cnblogs.com/f-ck-need-u/p/7576137.html
回到資料庫系列文章大綱:http://www.cnblogs.com/f-ck-need-u/p/7586194.html
轉載請註明出處:http://www.cnblogs.com/f-ck-need-u/p/8727401.html

註:若您覺得這篇文章還不錯請點擊右下角推薦,您的支持能激發作者更大的寫作熱情,非常感謝!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在WPF自學入門(十)WPF MVVM簡單介紹中的示例似乎運行起來沒有什麼問題,也可以進行更新。但是這並不是我們使用MVVM的正確方式。正如上一篇文章中在開始說的,MVVM的目的是為了最大限度地降低了Xaml文件和CS文件的耦合度,分離界面和業務邏輯,所以我們要儘可能的在View後臺不寫代碼。但是這 ...
  • 之前寫了一篇C#裝飾模式的文章提到了.NET Core的Stream, 所以這裡儘量把Stream介紹全點. (都是書上的內容) .NET Core/.NET的Streams 首先需要知道, System.IO命名空間是低級I/O功能的大本營. Stream的結構 .NET Core裡面的Strea ...
  • IOC(Inversion of Control):控制反轉 以下以課程與老師的安排來介紹控制反轉。 一個合理的課程編排系統應該圍繞培訓的內容為核心,而不應該以具體的培訓老師為核心,這樣才能在正常授課時可以隨意選取合適的老師來上課,而非綁定到一個老師身上。 一、探索IOC 1、最緊耦合度的編法 老師 ...
  • 在嵌入式系統行業用於評價CPU性能指標的標準主要有三種:Dhrystone、MIPS、CoreMark,其中CoreMark是一種新興流行的嵌入式系統處理器測試基準,被認為是比Dhrystone和MIPS更具有實際價值的測試基準。 ...
  • nginx的虛擬主機就是通過nginx.conf中server節點指定的,想要設置多個虛擬主機,配置多個server節點即可 先看一個最簡單的虛擬主機配置示例 虛擬主機名可以有4種格式: (1)準確的名字,例如此例中的a.test.com (2)\ 號開頭的,例如 \ .test.com (3)\ ...
  • 簡介 使用消息隊列可以在任務之間傳遞多條消息。消息隊列由三個部分組成:事件控制塊、消息隊列和消息。 當把事件控制塊成員 OSEventType 的值置為 OS_EVENT_TYPE_Q 時,該事件控制塊描述的就是一個消息隊列。 消息隊列相當於一個共用一個任務等待列表的消息郵箱數組,事件控制塊成員 O ...
  • 1 學習Linux的註意事項 1. 嚴格區分 大小寫 (命令, 文件, 選項) 2. Linux中所有內容以 文件形式 保存, 包括硬體 硬碟文件是/dev/sd[a p] 光碟文件是/dev/sr0等 3. Linux不靠 擴展名 區分文件類型, 靠的是 文件許可權 , 這個和windows不同. ...
  • linux作為一種老牌的開源系統,在幾十年間發展擴散出了三百餘種版本,很多都是我們耳熟能詳的,本篇主要介紹了linux的一些基礎發展由來歷史,市面上常見的幾種流行的linux版本,以及linux啟動的流程。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...