# Shell 高階 1 > 開始看不懂了,不要緊,就讓他過去吧,出事的時候就會看懂了 ## Shell :::spoiler 第一站:嵌入文檔 * 將資料傳遞給指令 ```bash= COMMAND <<InputComesFromHERE ... ... ... InputComesFromHERE ``` * 模擬人類的交互操作,下達命令 ```bash= interactive-program <<LimitString command #1 command #2 ... LimitString ``` * 如果要縮排美觀 * `<<`後加一個`-`號,這樣bash就會忽略tab * 注意這邊的模式不忽略space空格鍵 ```bash= COMMAND <<-InputComesFromHERE ... ... ... InputComesFromHERE ``` --- * 如果裡面有這些特殊符號,一般來說會展開 * 需要避免展開時可以將限制符包起來:`'...'` ```bash= COMMAND <<'InputComesFromHERE' $a `b` \c #這樣子這些$就不會被展開 InputComesFromHERE ``` * 空命令可以讓程式碼利用Here Documents來註解 ```bash= : <<InputComesFromHERE 註解內容 這邊就不用每行打#了 所以這個也蠻方便的 InputComesFromHERE ``` * 注意結束的限制符不能有空格 ```bash= COMMAND <<InputComesFromHERE ... ... ... InputComesFromHERE # 這裡加了空格,會報錯喔 ``` * 最後注意限制符儘量由多的字符組成避免報錯 ::: :::spoiler 第二站:I/O重新定向 1. 使用exec規避子shell ```bash= #我們今天要讀取檔案並抓取特定行時,會使用grep等管道 cat file.txt | grep apple #接下來我們透過設定變數,來使得file中的行加總 count=0 cat file.txt | while read line do {(( count++ ));} done #但是這樣的迴圈會產生子shell,縱使在迴圈計數,外部也不會變 #因此我們使用exec的定向,還規避子shell造成的影響 exec 3<>file.txt while read line <&3; do (( count++ )) done ``` 2. 代碼塊的重定向 ```bash= #while,for,until...等,在代碼塊結尾加上“<”進行重定向 until [ "$name" = Smith ] # 变 != 为 =. do read name # 从 $Filename 读取值, 而不是标准输入. echo $name done <"$Filename" # 重定向标准输入到文件 "$Filename". ``` 3. 嵌入文件 ```bash= function doesOutput() # 当然这也是个外部命令. # 这里用函数进行演示会更好一点. { ls -al *.jpg | awk '{print $5,$9}' }#這邊詳細介紹awk,ls將印出的資訊給到awk,awk依據欄位切割,並將第5&第9欄取出 nr=0 # 我们希望在 'while' 循环里可以操作这些 totalSize=0 # 并且在 'while' 循环结束时看到改变. while read fileSize fileName ; do#讀取第一個值和第二個值 echo "$fileName is $fileSize bytes"#印出來 let nr++ totalSize=$((totalSize+fileSize)) # Or: "let totalSize+=fileSize" done<<EOF $(doesOutput) EOF #EOF...EOF這一段最重要,將doesOutput的輸出給while echo "$nr files totaling $totalSize bytes" ``` --- 4. 限制模式 * 在限制模式下運行一個腳本或部分腳本將禁用一些命令,儘管這些命令在正常模式下是可用的。 這是個安全措施,可以限制腳本用戶的許可權,減少運行腳本可能帶來的損害。 * 那些限制: > 使用 cd 來改變工作目錄。 修改 $PATH, $SHELL, $BASH_ENV 或 $ENV 等環境變數 讀取或修改 $SHELLOPTS,shell環境選項。 輸出重定向。 調用包含 / 的命令。 調用 exec 來替代shell進程。 其他各種會造成混亂或顛覆腳本用途的命令。 在腳本中跳出限制模式。 ```bash= #!/bin/bash # 在脚本开头用"#!/bin/bash -r" #+ 可以让整个脚本在限制模式运行。 ~ # 到此为止一切都是正常的,非限制模式。 set -r # set --restricted 效果相同。 ~ # 如果想改目錄會跳test.sh: line xx: cd: restricted # 如果改shell會跳test.sh: line xx: SHELL: readonly variable ``` --- 5. 進程替換 * `>(command_list)`:把指令的「輸出」變成一個「可讀檔案」的樣子,供其他程式當作輸入讀取 * `<(command_list)`:把指令的「輸入」變成一個「可寫檔案」,其他程式可以把輸出寫到這裡 ```bash= #wc 的標準輸出格式為:行數 單詞數 字元數 [檔案路徑] $bash wc <(cat /usr/share/dict/linux.words) #cat的輸出作為檔案給了wc,因此輸出會像:483523 483523 4992010 /dev/fd/63 $bash grep script /usr/share/dict/linux.words | wc #因為輸入來自標準輸入(而不是檔案參數),wc 不會輸出檔案路徑:262 262 3601 ``` --- 6. 行程取代的一些用法 ```bash= #diff的使用,diff [檔案] [檔案],diff不支持標準輸入stdin diff <(command1) <(command2) #所以我們可以將指令的輸出臨時寫入檔案描述符 diff <(ls -l /bin) <(ls -l /usr) tar cf >(bzip2 -c > file.tar.bz2) $directory_name #這邊演示了命令中的進程替換 tar cf [dst] [src],語意為將src打包成tar給dst #tar會先把$directory_name打包成tar檔 #接著透過>()打包後的tar檔輸出,變成一個可讀檔案的樣子,給到bzip2 #bzip2用這個tar檔壓縮成file.tar.bz2 #以上的傳統示例 bzip2 -c < pipe > file.tar.bz2& #bzip2從pipe這個管道讀取,壓縮成file.tar.bz2,背景執行 tar cf pipe $directory_name #tar把$directory_name打包成tar並放在pipe,放過去後bzip2就會在背景看到,開始壓縮 rm pipe #結束後關閉管道pipe ``` --- 7. 子shell與管道 ```bash= echo "random input" | while read i do global=3D": Not available outside the loop." done echo "\$global (从子进程之外) = $global" #這個while會在|的子進程內,因此外部的主進程global不會有值 while read i do echo $i global=3D": Available outside the loop." done < <( echo "random input" ) echo "\$global (使用进程替换) = $global" #這個while透過進程替換,將echo的output暫時寫入檔案,再交給while執行 #這個while在主進程執行,因此global會改動到! ``` --- * 另一個有趣的? ```bash= ( route -n | while read x; do ((y++)); done echo $y # $y is still unset while read x; do ((y++)); done < <(route -n) echo $y # $y has the number of lines of output of route -n ) #這個是子shell中又有子shell #利用|的因為while是在子shell中的子shell運行,不會影響到外層的子shell,所以y不會變 #但是用進程替換的while就是在子shell進行,因此相同的,子shell的y會有值 ``` --- 8. &&和||的使用 * 透過&&讓if等語句少判斷,&&需要每次回傳true才會繼續執行,false終止,節省了許多判斷 ``` command-1 && command-2 && command-3 && ... command-n ``` * 透過||與&&語句相反,每次回傳false才會繼續執行,true則終止 ``` command-1 || command-2 || command-3 || ... command-n ``` ::: :::spoiler **補給站**:『定向』,『檔案描述符』 * 輸入定向`<`將檔案的內容作為命令的標準輸入 ```bash= command < file #讓命令從檔案讀取資料,而不是鍵盤 ``` * 輸入定向`<<`後續的多行文本作為命令的標準輸入 ```bash= command <<DELIMITER line 1... line 2... DELIMITER #提供多行輸入給命令,無需外部檔案 ``` * 輸出定向`>`將命令的『標準輸出』寫入檔案(覆蓋) ```bash= command > file #保存命令輸出到檔案,取代終端顯示 ``` * 輸出定向`>>`將命令的『標準輸出』追加至檔案(不覆蓋) ```bash= command >> file #累積命令輸出到檔案,適合日誌記錄 ``` --- * Bash中的檔案描述符 1. `0`stdin標準輸入 * 預設連接到鍵盤,用於程式讀取輸入。 2. `1`stdout標準輸出 * 預設連接到終端,用於程式輸出結果。 3. `2`stderr標準錯誤 * 預設連接到終端,用於輸出錯誤訊息。 * 修改檔案描述符的作用 * 讓程式知道該從哪邊接收輸入及輸出 ```bash= exec > file.txt echo "hi bro" #這行將hi bro寫到file.txt exec > 1 #這行有問題,因為 1 已經是標準輸出的檔案描述符,所以這相當於將標準輸出重定向到自身,這可能會導致非預期的行為。 echo "hello bro" ``` ```bash= exec 3>&1 # 保存原本的 stdout 到 FD 3 exec > file.txt echo "hi bro" # 寫入到 file.txt exec >&3 # 還原 stdout echo "hello bro" # 印回螢幕 exec 3>&- # 關閉 FD 3 #重定向標準輸出 ``` ```bash= exec 6<&0 exec < data-file exec 0<&6 6<&- #重定向標準輸入 ``` --- * 檔案描述符小總結 * 再將預設的檔案描述符更改前,先將預設的做備份(保存) * 當將預設的檔案描述符改回去後,要記得釋放 ::: --- 上一站:[Shell 進階](https://hackmd.io/a1Pm_wSlRmaKcmji61DiJg) 下一站:[Shell 高階 2](https://hackmd.io/IaYnG54rT8OzMBOQ1PRJ8g)
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up