Shell 命令輸入與輸出指南:掌握標準文件描述符與重定向 | 臨時文件

Shell 命令輸入與輸出指南:掌握標準文件描述符與重定向 | 臨時文件

Overview of Content

在 UNIX 和 Linux 系統中,Shell 是一個強大的工具,用於管理計算機系統和執行各種任務。本篇文章將深入探討 Shell 命令輸入和輸出的相關概念,以及如何通過重定向來有效地處理這些輸入和輸出

寫文章分享不易,如有引用參考請詳註出處,如有指導、意見歡迎留言(如果覺得寫得好也請給我一些支持),感謝 😀

個人程式分享時比較注重「縮排」,所以可能不適合手機的排版閱讀,建議切換至「電腦版」、「平板版」視窗看


Shell 命令輸入、輸出

Shell 的輸入、輸出方式有兩種(大方向):1. 輸出至螢幕、2. 輸出至檔案中;而這個輸入輸出是可以控制的,了解 Linux 輸入、輸出可以協助我們控制

描述符:標準文件的代號

● 在 Linux 系統中,「文件」通常被視為一個 檔案描述符(file descriptor它是一個用於識別打開文件的整數值;當應用程序需要讀取或寫入文件時,它會通過文件描述符與文件進行交互

● 其實 Linux 不只將文件視為文件,它也將進程、設被、Socket 視為文件

多進程 & 檔案描述符

多進程是一種應用程序的設計模式(並行範式),它可以讓一個應用程序同時運行多個獨立的進程(會有父子進程的概念)

在這些進程之間可以通過進程間通訊(IPC)機制進行通信和共享資源,例如共享內存區域或管道等

進程與文件之間是兩個不同的概念,簡單來說,檔案描述符是一個文件在系統種的唯一標示;進程則是系統對於一個應用規劃的最小單位

Shell 指令的文件描述符

● Linux shell 可以開啟的文件描述符數量是有限制的,然而 Shell 會保留文件描述符 012 用於指向不同文件,如下表

這裡 Shell 的文件描述符更像是一種指標,它會指向不同文件描述符

shell 文件描述符縮寫說明
0STDIN標準輸入
1STDOUT標準輸出
2STDERR標準錯誤

文件描述符數量有限智性

我們可以透過以下指令 查詢 & 設置文件描述符數量


# 查詢
ulimit -n [設置數量]

Shell 指令輸入、輸出、錯誤重定向

A. STDIN 標準輸入:一般來說終端機的 Input 就是鍵盤事件;而 Linux 可以透過重定向符 < 來改變輸入的流向

● 鍵盤作為 Input

● 文件作為 Input(以下把文件內容輸入到 cat 指令中)


cat < testFile

B. STDOUT 標準輸出在沒有錯誤的情況下,文件的輸出一般來說會指向螢幕;而 Linux 可以透過重定向符 >(覆蓋) & >>(追加)來達到輸出重定向

● 使用輸出覆蓋重定向 > 符號


ls -laF > myList.txt

cat myList.txt

# 覆蓋檔案內容
pwd > myList.txt

cat myList.txt

reference link

● 使用輸出追加重定向 >> 符號


cat myList.txt

# 追加檔案內容
whoami >> myList.txt

cat myList.txt

reference link

C. STDERR 標準錯誤:在 Shell 處理指令期間出錯時,錯誤訊息就會透過 STDERR 指向傳出,預設是指向螢幕輸出;Linux 需要重定向就必須指定檔案描述符 檔案描述符>

● 重新指定檔案描述符 2>2 是因為 Shell 保存的STDERR 就是 2


# 未重定向
ls -laF testFile myBook

# 重定向
ls -laF testFile myBook 2> stdErr

cat stdErr

● 我們可以多個重複定向

以下將標準錯誤輸出 2 導入到 stdErr 文件中,標準輸出 1 導入到 stdOut 文件中


# 重定向標準輸出、錯誤
ls -laF testFile myBook 2> stdErr 1> stdOut

cat stdOut

cat stdErr

特殊重定向 &>

透過 &> 符號可以將輸出、錯誤重定向到同一文件,並且該符號 它更注重於錯誤輸出,所以會將錯誤先輸出,再輸出一般數據


ls -laF testFile myBook &> stdOutAll

就是不按照一般順序,而是錯誤全部輸出完,才換到正常的內容


Shell 腳本中重定向

我們可以重新定義腳本的輸出訊息(也同樣分為輸入、輸出、錯誤訊息)

Shell 腳本中使用 FD:臨時重定向

● 在腳本內也可以使用 FD 重定向,其格式如下


輸出命令 >&FD

● 範例:輸出內容到錯誤 FD


#!/bin/bash

echo "Simulate the error" >&2

echo "Normal output" >&1

但由於 &2 也指向 &1 如果使用者不做任何處理看起來像是標準輸出

● 使用命令時,修改 FD 輸出就可以改變指定 FD 到指定檔案中


./shell_redirect.sh 2> shell_err

Shell 腳本中使用 FD:永久重定向(輸入輸出)

● 暫時重定向只能指定某個指令的輸出,無法一次全部指定該腳本的輸入、輸出導向(假設我需要指定全部指令的錯誤輸出到某個檔案中,那就要另外處理)

使用 exec 指令

該命令是用來 啟動一個新的子進程(sub shell)接管當前進程的 FD,來達到腳本的永久定向;其格式如下


exec FD>檔案

使用 exec 重定位後,就不可以再修改,除非你在重定位前有把 FD 存(暫存)起來

A. 使用 exec 範例:重定向輸出(普通輸出、錯誤輸出)


#!/bin/bash

PID=$(echo $$)

exec 2>errorMsg

# 仍可輸出在螢幕上
echo "Redirect the error to 'errorMsg'"

exec 1>normalMsg

# 已重新導向,不會輸出到螢幕上
echo "Redirect the normal to 'normalMsg'"

# 輸出到 normalMsg 中
echo "Normal msg ...~ $(echo $$)" >&1

# 輸出到 errorMsg 中
echo "Error msg !!!~ $(echo $$)" >&2

# 讀取不存在檔案,輸出錯誤到 errorMsg 中
cat Not_exist_file

B. 使用 exec 範例:重定向輸入


#!/bin/bash

# 0 就是標準輸入,將「errorMsg 檔案」作為輸入
exec 0< errorMsg

count=1

while read line ; do

    echo "Line=$count: $line"
    (( count++ ))

done

C. 使用 exec 範例:除了標準的 FD 以外,我們還可以重定向其他 FD 符


 #!/bin/bash

exec 5>fd_5

# 重定向輸出 FD 5
echo "Hello file describe of 5." >&5

echo "Normal output."

切換 FD 重定向

● 前面有提到永久重定向是不可改變的,這個前提是我們沒有把當前 Shell FD 的位置保存,就使用 exec 重向,導致遺失 FD;

知道這個原因後,就可以解決,在永久重定向之前暫存 FD 位置,

A. 輸出 FD 暫存/切換,範例如下


#!/bin/bash

echo "Before redirct the 1."

# 3 導向到 1
exec 3>&1

echo "Hello I'm 3." >&3

# 1 導向到 fd_1(無法正常輸出)
exec 1>fd_1

echo "After redirect the 1."

# 1 重新導向到 3(而 3 又導向 1,也就能正常輸出)
exec 1>&3

echo "Restore the 1"

概念圖如下

B. 輸入 FD 暫存/切換,範例如下


#!/bin/bash

# 將 0 的位置暫存到 9
exec 9<&0

# 將 testFile 作為輸入讀取
exec 0< testFile

while read line ; do

    echo "read: $line"

done

# 將輸入還給 0 fd(也就可以正常接收使用者鍵輸入)
exec 0<&9

read -p "Finish read file, do you want quit?" ans

case $ans in
    Y|y) echo "Bye" ;;
    N|n) echo "To do something" ;;
    *)   echo "???"
esac

Here 文件:<< 符號

Here 文件

把「標準輸入」重新定向到另一個指令(也就是把輸入直接賦予到令一個指令),這可以用來輸入多行指令;

使用 << 符號就可以控制 Here 文件,格式如下


#!/bin/sh

DATE=$(date)

cat << EOF
Date: $DATE

The output above is from the Unix date command.
EOF

● 以上使用 EOF 作為開頭、結尾用語(也可以使用其他字串,但是一定要成對出現

讀寫同 FD:<>

● 雖然很少這種情況(讀寫同 FD),但還是可以這樣操作;在同 FD 讀、寫時,Linux 會使用指針操作寫入的內容

範例如下


#!/bin/bash

# 重新 FD 3 的輸入、輸出到 testFile
exec 3<> testFile

read line <&3

echo "FD 3 read the 'testFile': $line"

echo "Write something to FD 3" >&3

● 這裡發生一種狀況,是原來的文件被覆蓋了,但可以發現 覆蓋的內容是依照指針移動的位置開始覆蓋

目前是因為讀取了 Hello 所以寫入時從 Hello 之後開始寫入


手動關閉 FD:&- 符號

● 一般來說,文件描述符的操作在 Shell 腳本結束之後就會自動關閉,如我們要想要提前關閉,可以使用以下指令格式關閉 FD


# 使用 ‵>&-‵
exec FD_NUM>&-

手動關閉 FD 範例


#!/bin/bash

exec 3> closeFdFile

echo "Hello shell script." >&3

exec 3>&-

echo "After close the fd 3." >&3

可以發現,關閉前的數據確實被輸出到檔案中,但關閉 FD 之後,仍要操作 FD 就會拋出錯誤

可以再次開啟同樣的 FD ?

可以,不過如果仍指定輸出相同文件,那舊有數據就會被覆蓋


#!/bin/bash

exec 3> closeFdFile2

# 這行數據會被覆蓋
echo "Hello shell script." >&3

exec 3>&-

# 開啟同個文件
exec 3> closeFdFile2

echo "After close, and open the fd 3." >&3

查看 FD 指向

有時我們會需要查看某個文件的 FD (文件描述符) 被定義到哪個位置上,那我們就可以 使用 lsof 命令 (list open files);其輸出的欄位意義如下表

輸出列表 Item說明補充
COMMAND正在運行的命令-
PID進程 PID-
USER文件所屬哪個進程-
FD當文件的檔案描述符 FD_<符號>其中符號 u 代表可讀寫、w 代表可寫、r 代表可讀
TYPE文件類型CAHR 字符、BLK 快設備、DIR 資料夾、REG 常規文件
DEVICE主/從設備號-
SIZE/OFF文件大小可有可無
NODE文件節點號-
NAME文件名-

# -a 代表之後的條件使用 AND 運算
# -p 指定進程
# -d 指定 FD
lsof -a -p $$ -d 0,1,2

腳本中使用 lsof 命令

● 以下使用在腳本中呼叫 lsof 命令,並查看相對應的 FD


#!/bin/bash

exec 3> testFile3
exec 6> testFile4
exec 7< testFile

lsof -a -p $$ -d 0,1,2,3,6,7

tmp 目錄

在根目錄下有一個 /tmp 臨時目錄,該目錄是用來存放暫時文件的,在該目錄下的文件,會在系統啟動時自動清除

創建暫存文件、目錄 - mktemp

● 透過 mktemp 命令,我們可以簡單地在當前目錄下創建文件,並且該文件還有以下特色

● 在當前目錄下 不重名

● 不受 umask 設定影響,直接將文件設定為創建者所有

一般文件會受到 umask 設定影響

並非所有 Unix 系統都有 mktemp 指令,可以透過 apt 命令安裝 coreutils

● 在本地使用 mktemp 指令創建只需要指定一個 文件名模板 即可;文件名模板 可以包含任意文件名,之後 末尾有 6 個 X 符號

格式如下


# 文件名模板

任意文件名.XXXXXX

A. 範例:創建隨機名檔案,並返回檔案名


mktemp myFile.XXXXXX

ls -laF

B. 範例:創建暫存目錄只需要加上 -d 選項即可,創建臨時目錄完畢後會 返回檔案名


mktemp -d myDir.XXXXXX

ls -laF

創建暫存文件:/tmp 目錄

● 同樣使用 mktemp 命令,不過這次 使用 -t 選項,透過這個選項就可以在 /tmp 目錄下創建暫存文件

A. 範例:創建暫存文件在 /tmp 目錄


mktemp -t myTmpFile.XXXXXX

B. 範例:使用在腳本中 mktemp 指令創建一個暫存文件,並對該文件輸入


#!/bin/bash

MY_TEMP_FILE=$(mktemp -t MyTempShell.XXXXXX)

echo "Hello world~" > $MY_TEMP_FILE

echo "Yo yo temp file~" >> $MY_TEMP_FILE

if [ -e $MY_TEMP_FILE ] ; then

    echo "Show $MY_TEMP_FILE"

    cat $MY_TEMP_FILE

    rm -f $MY_TEMP_FILE

else
    echo "$MY_TEMP_FILE not exists"
fi

其他

導向空文件:/dev/null

● 如果想忽略腳本中某些部分的錯誤,可以將 FD 2(STDERR) 重新導向到 /dev/null 文件上;

輸出到 /dev/null 的內容不會保存

● 範例:將指令錯誤導向 /dev/null


ls -laF NotExistFile testFile 2> /dev/null

同時紀錄 & 輸出:tee 命令

● 透過 tee 命令可以簡單的把內容 同時輸出到螢幕上、檔案中,其格式如下


tee 輸出檔案名

A. 範例:輸出結果至螢幕,並寫入到檔案


date | tee testTee

cat testTee

B. 範例:使用 -a 追加數據到指定檔案中

默認情況下 tee 會覆蓋檔案中的內容,如果要追加檔案內容需使用 -a 選項


who | tee -a testTee

cat testTee

更多 Linux Shell 知識

探索 Debian 系統上的套件管理系統(PKMS 概念)| 套件倉庫

Linux 環境、服務:用戶、權限管理

Linux 環境、服務、管理

在這個主題中,你可以探索各種 Linux 環境、服務和管理相關的重要概念…

了解 Linux 組成:內核責任、GNU 工具、桌面環境 | 基礎版到發行版

在這裡,你將了解 Linux 系統的基本組成部分,包括內核、GNU 工具和桌面環境,並了解不同 Linux 發行版之間的差異

探索 X Window 服務與桌面環境:Linux 視窗 XWindow 服務、D-Bus 機制

這裡探討了 Linux 系統中 X Window 服務和 D-Bus 機制的工作原理,讓你深入了解桌面環境的背後運作方式

Linux 系統管理入門:安全性、用戶管理與權限設定指南

在這個指南中,你將學習如何管理 Linux 系統,包括安全性、用戶管理和權限設定,以確保系統的安全和有效運作

認識身份驗證與權限管理:UNIX UID、使用者驗證與 PAM | PAM 設定

這裡介紹了 UNIX 系統中的身份驗證機制和權限管理方式,包括 UNIX UID、使用者驗證和 PAM(Pluggable Authentication Modules)設定

Linux Shell 相關知識

Linux Shell 相關知識

這個主題涵蓋了各種與 Linux Shell 相關的知識,從基礎到進階都有…

Shell 基礎知識:包括 Shell 的基礎使用以及差異,還有變數以及計算

認識命令行:Shell 類型、命令差異 | Sub Shell 關係 | Builtin 命令

這裡介紹了命令行中不同類型的 Shell,以及各種 Shell 命令之間的區別和內建命令的用法

Shell 全局及區域變數、特殊變數及環境變數 | Shell 啟動順序 | Array 變數

學習如何在 Shell 中使用全局變數、特殊變數和環境變數,以及如何設置和管理這些變數

探索腳本與命令:Shell 腳本的必備相關知識 | 腳本的數學運算

這裡介紹了 Shell 腳本的基本知識和常用技巧,包括如何執行腳本和進行數學運算

Shell 結構化

Shell 腳本程式中的條件語句和高級特性 | Shell 結構化 & 判斷

該篇文章是介紹結構化腳本的基礎以及特殊的高級技巧,轉著於腳本中的邏輯判斷

掌握 Bash 腳本中的迴圈與循環控制技巧 | Shell 結構化 & 循環

接著是腳本中的循環與控制的技巧,它可以讓我們在 Shell 腳本中有更多的判斷與技巧

探索 Shell 函數與腳本庫 | Shell 結構化 & 函數

這裡你將會學在到結構化腳本的重要技巧「函數」,它可以讓你建立可重複利用的腳本,以節省我們之後開發的時間

Shell 掌握參數處理與用戶輸入技巧 | Shell 結構化 &輸入互動

不可互動的腳本有時候相對無趣,這篇文章有分享如何讓腳本與使用者產生交互互動,並依照使用者輸入的參數做讀取

Shell script 進階

Shell 命令輸入與輸出指南:掌握標準文件描述符與重定向 | 臨時文件

掌控 Shell、指令的輸出輸入是成為進階使用 CLI 之人必經的一課,透過這篇文章可以了解到輸出輸入、臨時文件等等資訊

探索 Linux 訊號與後台進程管理:安排定期啟動腳本 | 運行時啟動腳本

信號可以算是 Shell 腳本與 Linux 系統之間的通訊方式,透過這個文章,我們可以了解到信號與 Shell 腳本的安排時間

Linux 硬體規劃:檔案系統、分區

Linux 硬體規劃:檔案系統、分區

最後,這個主題涵蓋了 Linux 系統中硬體結構相關的重要概念…

Shell 文件和目錄操作的常用命令和技巧 | 尋找檔案

這裡介紹了 Shell 中常用的文件和目錄操作命令,包括如何尋找文件和管理文件系統

深入探索 Linux 文件系統與硬體管理 | 分區、檔案系統、邏輯卷 | inode

學習如何管理 Linux 文件系統,包括分區、文件系統和邏輯卷等相關概念,以及文件系統中的 inode 機制

理解 Unix/Linux 設備偵測、建立 | 認識與使用 udevd | SCSI 與 Linux 核心

這裡介紹了 Unix/Linux 系統中的設備偵測和管理,包括如何使用 udevd 和 SCSI 相關的 Linux 核心知識

Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

發表迴響