如何在 Bash 中使用循環
人們希望學習批處理命令的一個普遍原因是要得到批處理強大的功能。如果你希望批量的對文件執行一些指令,構造一個可以重複運行在那些文件上的命令就是一種方法。在編程術語中,這被稱作執行控制,for
循環就是其中最常見的一種。
for
循環可以詳細描述你希望計算機對你指定的每個數據對象(比如說文件)所進行的操作。
一般的循環
使用循環的一個簡單例子是對一組文件進行分析。這個循環可能沒什麼用,但是這是一個安全的證明自己有能力獨立處理文件夾里每一個文件的方法。首先,創建一個文件夾然後拷貝一些文件(例如 JPEG、PNG 等類似的文件)至文件夾中生成一個測試環境。你可以通過文件管理器或者終端來完成創建文件夾和拷貝文件的操作:
$ mkdir example
$ cp ~/Pictures/vacation/*.{png,jpg} example
切換到你剛創建的那個新文件夾,然後列出文件並確認這個測試環境是你需要的:
$ cd example
$ ls -1
cat.jpg
design_maori.png
otago.jpg
waterfall.png
在循環中逐一遍歷文件的語法是:首先聲明一個變數(例如使用 f
代表文件),然後定義一個你希望用變數循環的數據集。在這種情況下,使用 *
通配符來遍歷當前文件夾下的所有文件(通配符 *
匹配所有文件)。然後使用一個分號(;
)來結束這個語句。
$ for f in * ;
取決於你個人的喜好,你可以選擇在這裡按下回車鍵。在語法完成前,shell 是不會嘗試執行這個循環的。
接下來,定義你想在每次循環中進行的操作。簡單起見,使用 file
命令來得到 f
變數(使用 $
告訴 shell 使用這個變數的值,無論這個變數現在存儲著什麼)所存儲著的文件的各種信息:
do file $f ;
使用另一個分號結束這一行,然後關閉這個循環:
done
按下回車鍵啟動 shell 對當前文件夾下所有東西的遍歷。for
循環將會一個一個的將文件分配給變數 f
並且執行你的命令:
$ for f in * ; do
> file $f ;
> done
cat.jpg: JPEG image data, EXIF standard 2.2
design_maori.png: PNG image data, 4608 x 2592, 8-bit/color RGB, non-interlaced
otago.jpg: JPEG image data, EXIF standard 2.2
waterfall.png: PNG image data, 4608 x 2592, 8-bit/color RGB, non-interlaced
你也可以用這種形式書寫命令:
$ for f in *; do file $f; done
cat.jpg: JPEG image data, EXIF standard 2.2
design_maori.png: PNG image data, 4608 x 2592, 8-bit/color RGB, non-interlaced
otago.jpg: JPEG image data, EXIF standard 2.2
waterfall.png: PNG image data, 4608 x 2592, 8-bit/color RGB, non-interlaced
對你的 shell 來說,多行和單行的格式沒有什麼區別,並且會輸出完全一樣的結果。
一個實用的例子
下面是一個循環在日常使用中的實用案例。假如你擁有一堆假期拍的照片想要發給你的朋友。但你的照片太大了,無法通過電子郵件發送,上傳到圖片分享服務也不方便。因此你想為你的照片創建小型的 web 版本,但是你不希望花費太多時間在一個一個的壓縮圖片體積上。
首先,在你的 Linux、BSD 或者 Mac 上使用包管理器安裝 ImageMagick 命令。例如,在 Fedora 和 RHEL 上:
$ sudo dnf install ImageMagick
在 Ubuntu 和 Debian 上:
$ sudo apt install ImageMagick
在 BSD 上,使用 ports
或者 pkgsrc 安裝。在 Mac 上,使用 Homebrew 或者 MacPorts 安裝。
在你安裝了 ImageMagick 之後,你就擁有一系列可以用來操作圖片的新命令了。
為你將要創建的文件建立一個目標文件夾:
$ mkdir tmp
使用下面的循環可以將每張圖片減小至原來大小的 33%。
$ for f in * ; do convert $f -scale 33% tmp/$f ; done
然後就可以在 tmp
文件夾中看到已經縮小了的照片了。
你可以在循環體中使用任意數量的命令,因此如果你需要對一批文件進行複雜的操作,可以將你的命令放在一個 for
循環的 do
和 done
語句之間。例如,假設你希望將所有處理過的圖片拷貝至你的網站所託管的圖片文件夾並且在本地系統移除這些文件:
$ for f in * ; do
convert $f -scale 33% tmp/$f
scp -i seth_web tmp/$f seth@example.com:~/public_html
trash tmp/$f ;
done
你的計算機會對 for
循環中處理的每一個文件自動的執行 3 條命令。這意味著假如你僅僅處理 10 張圖片,也會省下輸入 30 條指令和更多的時間。
限制你的循環
一個循環常常不需要處理所有文件。在示例文件夾中,你可能需要處理的只是 JPEG 文件:
$ for f in *.jpg ; do convert $f -scale 33% tmp/$f ; done
$ ls -m tmp
cat.jpg, otago.jpg
或者,你希望重複特定次數的某個操作而不僅僅只處理文件。for
循環的變數的值是被你賦給它的(不管何種類型的)數據所決定的,所以你可以創建一個循環遍曆數字而不只是文件:
$ for n in {0..4}; do echo $n ; done
0
1
2
3
4
更多循環
現在你了解的知識已經足夠用來創建自己的循環體了。直到你對循環非常熟悉之前,儘可能的在需要處理的文件的副本上進行操作。使用內置的保護措施可以預防損壞自己的數據和製造不可復現的錯誤,例如偶然將一個文件夾下的所有文件重命名為同一個名字,就可能會導致他們的相互覆蓋。
更進一步的 for
循環話題,請繼續閱讀。
不是所有的 shell 都是 Bash
關鍵字 for
是內置在 Bash shell 中的。許多類似的 shell 會使用和 Bash 同樣的關鍵字和語法,但是也有某些 shell ,比如 tcsh,使用不同的關鍵字,例如 foreach
。
tcsh 的語法與 Bash 類似,但是它更為嚴格。例如在下面的例子中,不要在你的終端的第 2、3 行鍵入 foreach?
。它只是提示你仍處在構建循環的過程中。
$ foreach f (*)
foreach? file $f
foreach? end
cat.jpg: JPEG image data, EXIF standard 2.2
design_maori.png: PNG image data, 4608 x 2592, 8-bit/color RGB, non-interlaced
otago.jpg: JPEG image data, EXIF standard 2.2
waterfall.png: PNG image data, 4608 x 2592, 8-bit/color RGB, non-interlaced
在 tcsh 中,foreach
和 end
都必須單獨的在一行中出現。因此你不能像 Bash 或者其他類似的 shell 一樣只使用一行命令創建一個 for
循環。
for 循環與 find 命令
理論上,你可能會用到不支持 for
循環的 shell,或者你只是更想使用其他命令的一些特性來完成和循環一樣的工作。
使用 find
命令是另一個實現 for
循環功能的途徑。這個命令提供了多種方法來定義循環中包含哪些文件的範圍以及並行處理的選項。
find
命令顧名思義就是幫助你查詢存儲在硬碟里的文件。它的用法很簡單:提供一個你希望它查詢的位置的路徑,接著 find
就會查詢這個路徑下面的所有文件和文件夾。
$ find .
.
./cat.jpg
./design_maori.png
./otago.jpg
./waterfall.png
你可以通過添加名稱的某些部分來過濾搜索結果:
$ find . -name "*jpg"
./cat.jpg
./otago.jpg
find
命令非常好的地方在於你可以通過 -exec
參數標誌將它查詢到的每一個文件放入循環中。例如,只對存放在你的 example
文件夾下的 PNG 圖片進行體積壓縮操作:
$ find . -name "*png" -exec convert {} -scale 33% tmp/{} ;
$ ls -m tmp
design_maori.png, waterfall.png
在 -exec
短語中,括弧 {}
表示的是 find
正在處理的條目(換句話說,每一個被找到的以 PNG 結尾的文件)。-exec
短語必須使用分號結尾,但是 Bash 中常常也會使用分號。為了解決這個二義性問題,你的 結束符
可以使用反斜杠加上一個分號(;
),使得 find
命令可以知道這個結束符是用來標識自己結束使用的。
find
命令的操作非常棒,某些情況下它甚至可以表現得更棒。比如說,在一個新的進程中使用同一條命令查找 PNG 文件,你可能就會得到一些錯誤信息:
$ find . -name "*png" -exec convert {} -flip -flop tmp/{} ;
convert: unable to open image `tmp/./tmp/design_maori.png':
No such file or directory @ error/blob.c/OpenBlob/2643.
...
看起來 find
不只是定位了當前文件夾(.
)下的所有 PNG 文件,還包括已經處理並且存儲到了 tmp
下的文件。在一些情況下,你可能希望 find
查詢當前文件夾下再加上其子文件夾下的所有文件。find
命令是一個功能強大的遞歸工具,特別體現在處理一些文件結構複雜的情境下(比如用來放置存滿了音樂人音樂專輯的文件夾),同時你也可以使用 -maxdepth
選項來限制最大的遞歸深度。
只在當前文件夾下查找 PNG 文件(不包括子文件夾):
$ find . -maxdepth 1 -name "*png"
上一條命令的最大深度再加 1 就可以查找和處理當前文件夾及下一級子文件夾下面的文件:
$ find . -maxdepth 2 -name "*png"
find
命令默認是查找每一級文件夾。
循環的樂趣與收益
你使用的循環越多,你就可以越多的省下時間和力氣,並且可以應對龐大的任務。雖然你只是一個用戶,但是通過使用循環,可以使你的計算機完成困難的任務。
你可以並且應該就像使用其他的命令一樣使用循環。在你需要重複處理單個或多個文件時,儘可能的使用這個命令。無論如何,這也算是一項需要被嚴肅對待的編程活動,因此如果你需要在一些文件上完成複雜的任務,你應該多花點時間在規劃自己的工作流上面。如果你可以在一份文件上完成你的工作,接下來將操作包裝進 for
循環里就相對簡單了,這裡面唯一的「編程」的需要只是理解變數是如何工作的並且進行充分的規劃工作將已處理過的文件和未處理過的文件分開。經過一段時間的練習,你就可以從一名 Linux 用戶升級成一位知道如何使用循環的 Linux 用戶,所以開始讓計算機為你工作吧!
via: https://opensource.com/article/19/6/how-write-loop-bash
作者:Seth Kenlon 選題:lujun9972 譯者:chunibyo-wly 校對:wxy
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive