1. 程式人生 > 其它 >shell指令碼程式設計筆記(六)—— 輸出處理

shell指令碼程式設計筆記(六)—— 輸出處理

技術標籤:shelllinux

一、標準檔案描述符

Linux將每個物件當作檔案處理,包括輸入和輸出程序。Linux用檔案描述符(file descriptor)來標識每個檔案物件,檔案描述符是一個非負整數,可以唯一標識會話中開啟的檔案。每個程序一次最多可以有9個檔案描述符,bash shell保留了前3個(0,1,2),這三個被稱為標準檔案描述符。

1. STDIN

STDIN檔案描述符代表shell的標準輸入,對終端介面來說,標準輸入是鍵盤。許多bash命令能接受STDIN的輸入,當在命令列上只輸入cat命令時,它會從鍵盤接受輸入。輸入一行, cat就顯示出一行。

在使用輸入重定向符號<時,Linux會用重定向指定的檔案來替換標準輸入檔案描述符,因此它會讀取檔案並提取資料。

可以通過<強制cat命令接受檔案輸入,跟不加<符號效果是一樣的。你可以使用這種技術將資料輸入到任何能從STDIN接受資料的shell命令中。

2. STDOUT

STDOUT檔案描述符代表shell的標準輸出,在終端介面上,標準輸出就是終端顯示器。 shell的所有輸出(包括shell中執行的程式和指令碼)預設會被定向到標準輸出,也就是顯示器中。可以使用 > 符號改變輸出位置,通常會指向一個檔案。

  • ls -l > test2 #覆蓋原資料
  • ls -l >> test2 #追加內容

但是,如果執行的命令有報錯,STDOUT並不能重定向報錯內容,例如

$ ls -al badfile > test3
ls: cannot access badfile: No such file or directory
$ cat test3
$

當命令生成錯誤訊息時, shell並未將錯誤訊息重定向到指定檔案,而是顯示在了顯示器螢幕上。注意,在顯示test3檔案的內容時並沒有任何錯誤。 test3檔案建立成功了,只是裡面是空的。shell對於錯誤訊息的處理是跟普通輸出分開的,想要重定向錯誤資訊,你需要換種方法來處理。

3. STDERR

STDERR檔案描述符代表shell的標準錯誤輸出,命令和shell指令碼出錯時生成的錯誤訊息都會發送到這個位置。預設情況下, STDERR和STDOUT都指向顯示器,儘管分配給它們的檔案描述符值不同。你已經知道如何用重定向符號來重定向STDOUT資料。重定向STDERR資料也沒太大差別,只要在使用重定向符號時定義

STDERR檔案描述符就可以了。有幾種辦法實現方法:

  • 只重定向錯誤

STDERR檔案描述符為2,只重定向錯誤訊息,將該檔案描述符值放在重定向符號前即可(不要有空格)。

ls -al badfile 2> test4

如果有正常輸出也有錯誤輸出,這種方法只能重定向錯誤資訊至檔案,正常資訊依然會輸出到顯示器。

ls -al test badtest test4 2> test5

  • 重定向錯誤和資料

如果想重定向錯誤和正常輸出到不同檔案,需要用兩個重定向符號。

ls -al badtest test4 2> test5 1> test6

也可以將STDERR和STDOUT的輸出重定向到同一個輸出檔案,bash shell提供了特殊的重定向符號&>。

ls -al badtest test4 &> test7

為了避免錯誤資訊散落在輸出檔案中,相較於標準輸出, bash shell自動賦予了錯誤訊息更高的優先順序,方便集中瀏覽錯誤資訊。

二、 自定義檔案描述符

前面提到過,在shell中最多可以有9個開啟的檔案描述符,3~8的檔案描述符均可用作輸入或輸出重定向。你可以將這些檔案描述符中的任意一個分配給檔案,然後在指令碼中使用它們。

1.建立輸出檔案描述符

可以用exec命令來給輸出分配檔案描述符,分配之後這個重定向就會一直有效,直到你重新分配或者關閉。

$ cat test13
#!/bin/bash
# using an alternative file descriptor

exec 3>test13out
echo "This should display on the monitor"
echo "and this should be stored in the file" >&3
echo "Then this should be back on the monitor"


$ ./test13
This should display on the monitor
Then this should be back on the monitor
$ cat test13out
and this should be stored in the file

當指令碼執行echo語句時,輸出內容會像預想中那樣顯示在STDOUT上,但重定向到檔案描述符3的那行echo語句的輸出卻進入了另一個檔案。

2. 重定向檔案描述符

現在介紹怎麼恢復已重定向的檔案描述符。你可以分配另外一個檔案描述符給標準檔案描述符,反之亦然。這意味著你可以將STDOUT的原來位置重定向到另一個檔案描述符,然後再利用該檔案描述符重定向回STDOUT

$ cat test14
#!/bin/bash
# storing STDOUT, then coming back to it

exec 3>&1 #將檔案描述符3重定向到檔案描述符1,也就是STDOUT。這意味著任何傳送給檔案描述符3的輸出都將出現在顯示器上。
exec 1>test14out #將檔案描述符1重定向到檔案,但是,檔案描述符3仍然指向原來的位置(顯示器)。如果此時將輸出資料傳送給檔案描述符3,它仍然會出現在顯示器上,儘管檔案描述符1已經被重定向了。
echo "This should store in the output file"
echo "along with this line."

exec 1>&3 #將檔案描述符1重定向到檔案描述符3的當前位置(顯示器),這意味著現在檔案描述符1又指向了它原來的位置:顯示器。
echo "Now things should be back to normal"

$ ./test14
Now things should be back to normal
$ cat test14out
This should store in the output file
along with this line.

3. 關閉檔案描述符

如果你建立了新的輸入或輸出檔案描述符, shell會在指令碼退出時自動關閉它們。然而在有些情況下,你需要在指令碼結束前手動關閉檔案描述符。要關閉檔案描述符,需要將它重定向到特殊符號&-。

exec 3>&-

一旦關閉了檔案描述符,就不能在指令碼中向它寫入任何資料,否則shell會報錯。

$ cat badtest
#!/bin/bash
# testing closing file descriptors
exec 3> test17file
echo "This is a test line of data" >&3
exec 3>&-
echo "This won't work" >&3

$ ./badtest
./badtest: 3: Bad file descriptor

4. 列出開啟的檔案描述符

lsof命令會列出整個Linux系統開啟的所有檔案描述符。要想以普通使用者賬戶來執行它,必須通過全路徑名來引用:/usr/sbin/lsof。

有大量的命令列選項和引數可以用來幫助過濾lsof的輸出。最常用的有-p-d,前者允許指定PID,後者允許指定要顯示的檔案描述符編號。

要想知道程序的當前PID,可以用特殊環境變數$$-a選項用來對其他兩個選項的結果執行AND運算。

/usr/sbin/lsof -a -p $$ -d 0,1,2

COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
bash 3344 rich 0u CHR 136,0 2 /dev/pts/0
bash 3344 rich 1u CHR 136,0 2 /dev/pts/0
bash 3344 rich 2u CHR 136,0 2 /dev/pts/0

5. 阻止命令輸出

有時候,你可能不想顯示指令碼的輸出。可以將STDERR重定向到一個叫作null檔案的特殊檔案。Linux系統上null檔案的標準位置是/dev/null,重定向到該位置的任何資料都會被丟掉,不會顯示。

ls -al > /dev/null
cat /dev/null

這是避免輸出錯誤訊息,也無需儲存它們的一個常用方法。

ls -al badfile test16 2> /dev/null
-rwxr--r-- 1 rich rich 135 Oct 29 19:57 test16*

也可以將/dev/null作為輸入檔案,由於/dev/null檔案不含有任何內容,通常用來快速清除現有檔案中的資料,而不用先刪除檔案再重新建立。

$ cat testfile
This is the first line.
This is the second line.
This is the third line.
$ cat /dev/null > testfile
$ cat testfile

檔案testfile仍然存在系統上,但現在它是空檔案。這是清除日誌檔案的一個常用方法,因為日誌檔案必須時刻準備等待應用程式操作。

6. 記錄訊息

想要將輸出同時傳送到顯示器和日誌檔案,不用將輸出重定向兩次,只要用特殊的tee命令就行。tee命令相當於管道的一個T型接頭。它將從STDIN過來的資料同時發往兩處:STDOUT和tee命令指定的檔名。

$ date | tee testfile
Sun Oct 19 18:56:21 EDT 2014
$ cat testfile
Sun Oct 19 18:56:21 EDT 2014

預設情況下, tee命令會在每次使用時覆蓋輸出檔案內容。如果你想將資料追加到檔案中,必須用-a選項。

$ date | tee -a testfile
Sun Oct 19 18:58:05 EDT 2014

$ cat testfile
Sun Oct 19 18:56:21 EDT 2014
Sun Oct 19 18:58:05 EDT 2014

三、 例項

檔案重定向常見於指令碼需要讀入檔案和輸出檔案時。這個樣例指令碼兩件事都做了。它讀取.csv格式的資料檔案,輸出SQL INSERT語句來將資料插入資料庫

tran.sh

#!/bin/bash
# read file and create INSERT statements for MySQL
outfile='members.sql'
IFS=','
cat $1 | while read lname fname address city state zip
do
cat >> $outfile << EOF
INSERT INTO members (lname,fname,address,city,state,zip) VALUES
('$lname', '$fname', '$address', '$city', '$state', '$zip');
EOF
done

members.csv

Blum,Richard,123 Main St.,Chicago,IL,60601
Blum,Barbara,123 Main St.,Chicago,IL,60601
Bresnahan,Christine,456 Oak Ave.,Columbus,OH,43201
Bresnahan,Timothy,456 Oak Ave.,Columbus,OH,43201

執行指令碼時,顯示器上不會出現任何輸出, 但是在members.sql輸出檔案中,你會看到如下輸出內容。

chmod +x tran.sh
./tran.sh < members.csv
cat members.sql