跳轉到內容

Bourne Shell 指令碼/附錄 D: 食譜

來自華夏公益教科書,開放的書籍,開放的世界
維基共享資源徽標 釋出新的食譜條目
如果您使用標題框,則無需在正文中放置標題。

基於副檔名分支

[編輯 | 編輯原始碼]

在編寫應根據副檔名執行不同操作的 bash 指令碼時,以下模式很有用。

 #filepath should be set to the name(with optional path) of the file in question
 ext=${filepath##*.}
 if [[ "$ext" == txt ]] ; then 
   #do something with text files
 fi

(來源:slike.com Bash FAQ).

重新命名多個檔案

[編輯 | 編輯原始碼]

此食譜演示瞭如何按照模式重新命名多個檔案。

在這個例子中,使用者有大量的螢幕截圖。該使用者想要使用 Bourne 相容的 shell 重新命名這些檔案。這是一個在 shell 提示符下顯示檔名的 "ls"。目標是將像 "snapshot1.png" 這樣的影像重新命名為 "nethack-kernigh-22oct2005-01.png"

 $ ls
 snapshot1.png   snapshot25.png  snapshot40.png  snapshot56.png  snapshot71.png
 snapshot10.png  snapshot26.png  snapshot41.png  snapshot57.png  snapshot72.png 
 snapshot11.png  snapshot27.png  snapshot42.png  snapshot58.png  snapshot73.png
 snapshot12.png  snapshot28.png  snapshot43.png  snapshot59.png  snapshot74.png
 snapshot13.png  snapshot29.png  snapshot44.png  snapshot6.png   snapshot75.png
 snapshot14.png  snapshot3.png   snapshot45.png  snapshot60.png  snapshot76.png
 snapshot15.png  snapshot30.png  snapshot46.png  snapshot61.png  snapshot77.png
 snapshot16.png  snapshot31.png  snapshot47.png  snapshot62.png  snapshot78.png
 snapshot17.png  snapshot32.png  snapshot48.png  snapshot63.png  snapshot79.png
 snapshot18.png  snapshot33.png  snapshot49.png  snapshot64.png  snapshot8.png
 snapshot19.png  snapshot34.png  snapshot5.png   snapshot65.png  snapshot80.png
 snapshot2.png   snapshot35.png  snapshot50.png  snapshot66.png  snapshot81.png
 snapshot20.png  snapshot36.png  snapshot51.png  snapshot67.png  snapshot82.png
 snapshot21.png  snapshot37.png  snapshot52.png  snapshot68.png  snapshot83.png
 snapshot22.png  snapshot38.png  snapshot53.png  snapshot69.png  snapshot9.png
 snapshot23.png  snapshot39.png  snapshot54.png  snapshot7.png
 snapshot24.png  snapshot4.png   snapshot55.png  snapshot70.png

首先,要在快照 1 到 9 之前新增 "0"(零),編寫一個 for 迴圈(實際上是一個簡短的 shell 指令碼)。

  • 使用?這是一個單個字元的檔名模式。使用它,我可以匹配快照 1 到 9,但可以透過說來錯過 10 到 83snapshot?.png.
  • 使用${引數#模式}用從開頭刪除的模式替換引數的值。這是為了去掉 "snapshot",以便我可以放入 "snapshot0"。
  • 在實際執行迴圈之前,插入一個 "echo" 來測試命令是否正確。
 $ for i in snapshot?.png; do echo mv "$i" "snapshot0${i#snapshot}"; done
 mv snapshot1.png snapshot01.png
 mv snapshot2.png snapshot02.png
 mv snapshot3.png snapshot03.png
 mv snapshot4.png snapshot04.png
 mv snapshot5.png snapshot05.png
 mv snapshot6.png snapshot06.png
 mv snapshot7.png snapshot07.png
 mv snapshot8.png snapshot08.png
 mv snapshot9.png snapshot09.png

看起來不錯,所以透過刪除 "echo" 來執行它。

 $ for i in snapshot?.png; do mv "$i" "snapshot0${i#snapshot}"; done

一個 ls 證實了這很有效。

現在將字首 "snapshot" 更改為 "nethack-kernigh-22oct2005-"。執行一個類似於上一個迴圈的迴圈

 $ for i in snapshot*.png; do \
 > mv "$i" "nethack-kernigh-22oct2005-${i#snapshot}" \
 > done

這為使用者節省了鍵入 83 個 "mv" 命令的麻煩。

長命令列選項

[編輯 | 編輯原始碼]

內建的getopts不支援長選項,因此需要外部的getopt是必需的。(在某些系統上,getopt 不支援長選項,因此下一個示例將無法正常工作。)

eval set -- $(getopt -l install-opts: "" "$@")
while true; do
    case "$1" in
        --install-opts)
            INSTALL_OPTS=$2
            shift 2
            ;;
        --)
            shift
            break
            ;;
    esac
done

echo $INSTALL_OPTS

getopt引用並重新排序在$@. set中找到的命令列引數,然後用$@的輸出替換getopt

高階 Bash 指令碼指南中也可以找到 getopt 使用的另一個示例

透過 xargs 處理特定檔案

[編輯 | 編輯原始碼]

在這個食譜中,我們想要處理一個大型檔案列表,但我們必須為每個檔案執行一個命令。在這個例子中,我們想要將一些聲音檔案的取樣率轉換為 44100 赫茲。命令是sox file.ogg -r 44100 conv/file.ogg,它將file.ogg轉換為一個新檔案conv/file.ogg。我們還想要跳過已經為 44100 赫茲的檔案。

首先,我們需要我們檔案的取樣率。一種方法是使用file命令

 $ file *.ogg
 audio_on.ogg:            Ogg data, Vorbis audio, mono, 44100 Hz, ~80000 bps
 beep_1.ogg:              Ogg data, Vorbis audio, stereo, 44100 Hz, ~193603 bps
 cannon_1.ogg:            Ogg data, Vorbis audio, mono, 48000 Hz, ~96000 bps
 ...

(此示例中的檔案來自 秘密瑪麗奧編年史。) 我們可以使用grep -v過濾掉所有包含 '44100 Hz' 的行

 $ file *.ogg | grep -v '44100 Hz'
 cannon_1.ogg:            Ogg data, Vorbis audio, mono, 48000 Hz, ~96000 bps
 ...
 jump_small.ogg:          Ogg data, Vorbis audio, mono, 8000 Hz, ~22400 bps
 live_up.ogg:             Ogg data, Vorbis audio, mono, 22050 Hz, ~40222 bps
 ...

我們使用 "grep" 和 "file" 完成了,所以現在我們想要刪除其他資訊,只留下要傳遞給 "sox" 的檔名。我們使用文字實用程式cut。選項-d將每行在冒號處分成欄位;-f1選擇第一個欄位。

 $ file *.ogg | grep -v '44100 Hz' | cut -d: -f1
 cannon_1.ogg
 ...
 jump_small.ogg
 live_up.ogg
 ...

我們可以使用另一個管道來提供標準輸入上的檔名,但 "sox" 將它們作為引數。我們使用xargs,它將使用來自標準輸入的引數重複執行命令。該-n1選項指定每個命令一個引數。例如,我們可以執行echo sox重複

 $ file *.ogg | grep -v '44100 Hz' | cut -d: -f1 | xargs -n1 echo sox
 sox cannon_1.ogg
 ...
 sox itembox_set.ogg
 sox jump_small.ogg
 ...

但是,這些命令是錯誤的。例如,cannon_1.ogg 的完整命令是sox cannon_1.ogg -r 44100 conv/cannon_1.ogg。"xargs" 將插入傳入資料到由 " {} " 表示的佔位符中。我們在此管道中使用此策略。如果有疑問,首先我們可以使用 "echo" 構建一個測試管道

 $ file *.ogg | grep -v '44100 Hz' | cut -d: -f1 | \
 > xargs -i 'echo sox {} -r 44100 conv/{}'
 sox cannon_1.ogg -r 44100 conv/cannon_1.ogg
 ...
 sox itembox_set.ogg -r 44100 conv/itembox_set.ogg
 sox jump_small.ogg -r 44100 conv/jump_small.ogg
 ...

它起作用了,所以讓我們刪除 "echo" 並執行 "sox" 命令

 $ mkdir conv
 $ file *.ogg | grep -v '44100 Hz' | cut -d: -f1 | \
 > xargs -i 'sox {} -r 44100 conv/{}'

等待一段時間後,轉換後的檔案將出現在conv子目錄中。以上三行程式碼單獨完成了整個轉換。

GStreamer 的簡單播放列表前端

[編輯 | 編輯原始碼]

如果您有 GStreamer,則命令gst-launch filesrc location=filename ! decodebin ! audioconvert ! esdsink將播放任何格式的聲音或音樂檔案,只要您擁有 GStreamer 外掛。此指令碼將播放檔案列表,可以選擇迴圈播放它們。(將 "esdsink" 替換為您最喜歡的接收器。)

#!/bin/sh
loop=false
if test x"$1" == x-l; then
  loop=true
  shift
fi

while true; do
  for i in "$@"; do
    if test -f "$i"; then
      echo "${0##*/}: playing $i" > /dev/stderr
      gst-launch filesrc location="$i" ! decodebin ! audioconvert ! esdsink
    else
      echo "${0##*/}: not a file: $i" > /dev/stderr
    fi
  done
  if $loop; then true; else break; fi
done

此指令碼演示了一些常見的 Bourne shell 策略

  • "loop" 是一個布林變數。它之所以有效是因為它的值 "true" 和 "false" 都是 Unix 命令(有時也是 shell 內建命令),因此您可以在ifwhile語句中使用它們作為條件。
  • shell 內建命令 "shift" 從引數列表中刪除 $1,從而將 $2 移到 $1,將 $3 移到 $2,等等。此指令碼使用它來處理 "-l" 選項。
  • 替換${0##*/}提供 $0 中最後一個斜線後的所有內容,因此是 "playlist",而不是 "/home/musicfan/bin/playlist"。


下一頁: Bourne Shell 指令碼 | 上一頁: 附錄 C: 快速參考
首頁: Bourne Shell 指令碼
華夏公益教科書