如何在Bash中編寫循環?

如何在Bash中編寫循環?

使用for循環和find命令自動對多個文件執行一組操作。

人們想要學習Unix shell的一個常見原因是釋放批處理的功能。如果要對許多文件執行某些操作,一種方法是構造一個遍歷這些文件的命令來實現。在編程術語中,這稱爲執行控制,最常見的示例之一是for循環。

for循環是一個配方,詳細說明了您希望計算機對指定的每個數據對象(例如文件)執行什麼操作。

經典的循環

Linux終端適用於Linux的7大終端仿真器用於Linux中進行數據分析的10個命令行工具立即下載:SSH備忘單高級Linux命令備忘單Linux命令行教程一個簡單的循環是分析文件集合的循環。這本身可能不是一個有用的循環,但它是一種安全的方法,可以向您證明自己有能力分別處理目錄中的每個文件。首先,通過創建目錄並將一些文件的某些副本放入其中來創建一個簡單的測試環境。一開始的時候使用任何文件都可以,但是以後的示例需要圖形文件(例如JPEG、PNG或類似文件)。您可以使用文件管理器或在終端中創建文件夾並將文件複製到其中:

  •  
  •  
$ mkdir example        $ cp ~/Pictures/vacation/*.{png,jpg} example

將目錄更改爲新文件夾,然後列出其中的文件以確認測試環境符合您的期望:

  •  
  •  
  •  
  •  
  •  
  •  
$ cd example$ ls -1cat.jpgdesign_maori.pngotago.jpgwaterfall.png

在一個循環中逐個遍歷每個文件的語法是:創建一個變量。然後定義您要變量循環通過的數據集。在這種情況下,請使用通配符循環瀏覽當前目錄中的所有文件(通配符匹配所有內容)。然後以分號(;)終止此介紹性子句。

  •  
$ for f in * ;

根據您的喜好,您可以選擇按此處返回。在語法上完成之前,shell不會嘗試執行循環。

接下來,定義您希望在每次循環迭代中發生的事情。爲簡單起見,請使用file命令獲取有關每個文件的少量數據,這些數據由f變量表示(但是以$開頭,告訴shell將變量的值替換爲當前包含的變量):

  •  
do file $f ;

用另一個分號終止子句並關閉循環:

  •  
done

做完了按Return鍵可啓動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

多行和單行格式對於您的外殼都是相同的,並且產生完全相同的結果。

一個實際的例子

 

這是一個循環如何對日常計算有用的實際示例。假設您有要發送給朋友的度假照片集。您的照片文件很大,太大而無法通過電子郵件發送,並且不便上傳到您的照片共享服務。您想爲照片創建較小的網絡版本,但是您有100張照片,不想浪費時間一張一張地縮小每張照片。

首先,在Linux,BSD或Mac上使用包管理器安裝ImageMagick命令。例如,在Fedora和RHEL上:

  •  
$ sudo dnf install ImageMagick

在Ubuntu或Debian上:

  •  
$ sudo apt install ImageMagick

在BSD上,使用端口或pkgsrc。在Mac上,使用Homebrew或MacPorts。

安裝ImageMagick後,您將擁有一組用於對照片進行操作的新命令。

爲您要創建的文件創建目標目錄:

  •  
$ mkdir tmp

要將每張照片縮小到其原始大小的33%,請嘗試以下循環:

  •  
$ for f in * ; do convert $f -scale 33% tmp/$f ; done

然後在tmp文件夾中查看縮放後的照片。

您可以在循環中使用任意數量的命令,因此,如果您需要對一批文件執行復雜的操作,則可以將整個工作流放在for循環的do和done語句之間。例如,假設您要將每張處理過的照片直接複製到Web主機上的共享照片目錄,並從本地系統中刪除照片文件:

  •  
  •  
  •  
  •  
  •  
$ for f in * ; do    convert $f -scale 33% tmp/$f    scp -i seth_web tmp/$f [email protected]:~/public_html    trash tmp/$f ;  done

做完了對於for循環處理的每個文件,您的計算機將自動運行三個命令。這意味着,如果您僅以這種方式處理10張照片,則可以爲自己節省30條命令,還會節省同樣多的時間。

限制循環

並不一定總是要查看每個文件。您可能只想處理示例目錄中的JPEG文件:

  •  
  •  
  •  
$ for f in *.jpg ; do convert $f -scale 33% tmp/$f ; done$ ls -m tmpcat.jpg, otago.jpg

做完了 ls -m tmpcat.jpg,otago.jpg或者,您可能需要重複執行特定次數的操作,而不是處理文件。for循環的變量由您提供的任何數據定義,因此您可以創建一個循環訪問迭代數字而不是文件的循環:

  •  
  •  
  •  
  •  
  •  
  •  
$ for n in {0..4}; do echo $n ; done01234

更多的循環

您現在已經足夠了解創建自己的循環了。在對循環感到滿意之前,請在要處理的文件副本上使用它們,並儘可能多地使用帶有內置保護措施的命令,以防止您破壞數據並造成不可彌補的錯誤,例如意外重命名整個文件,相同名稱的文件目錄,彼此覆蓋。

有關高級for循環主題,請繼續閱讀。

並非所有的shell都是Bash

for關鍵字內置在Bash shell中。許多相似的shell使用相同的關鍵字和語法,但是某些shell(例如tcsh)使用不同的關鍵字(例如foreach)來代替。

在tcsh中,語法本質上相似,但比Bash嚴格。在以下代碼示例中,是否不鍵入字符串foreach?在第2行和第3行中。它是輔助提示,提醒您仍在構建循環的過程中。

  •  
  •  
  •  
  •  
  •  
  •  
  •  
$ foreach f (*)foreach? file $fforeach? endcat.jpg: JPEG image data, EXIF standard 2.2design_maori.png: PNG image data, 4608 x 2592, 8-bit/color RGB, non-interlacedotago.jpg: JPEG image data, EXIF standard 2.2waterfall.png: PNG image data, 4608 x 2592, 8-bit/color RGB, non-interlaced

在tcsh中,foreach和end都必須單獨出現在單獨的行中,因此不能像使用Bash和類似的shell那樣在一行上創建for循環。

使用find命令執行for循環

從理論上講,您可能會發現一個不提供for循環函數的shell,或者您可能只是更喜歡使用帶有附加功能的其他命令。

find命令是實現for循環功能的另一種方法,因爲它提供了幾種方法來定義要包含在循環中的文件範圍以及並行處理選項。

find命令旨在幫助您在硬盤驅動器上查找文件。它的語法很簡單:您提供要搜索的位置的路徑,並找到所有文件和目錄:

  •  
  •  
  •  
  •  
  •  
  •  
$ find .../cat.jpg./design_maori.png./otago.jpg./waterfall.png

你可以通過添加name的一部分來過濾搜索結果:

  •  
  •  
  •  
$ find . -name "*jpg"./cat.jpg./otago.jpg

find的優點在於,可以使用-exec標誌將找到的每個文件輸入到循環中。例如,要僅縮小示例目錄中的PNG照片,請執行以下操作:

  •  
  •  
  •  
$ find . -name "*png" -exec convert {} -scale 33% tmp/{} \;$ ls -m tmpdesign_maori.png, waterfall.png

在-exec子句中,括號字符{}代表正在處理的任何項(換句話說,已定位的任何以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子目錄中的文件。在某些情況下,您可能想要搜索當前目錄以及其中的所有其他目錄(以及其中的所有目錄)。它可以是功能強大的遞歸處理工具,尤其是在複雜的文件結構中(例如,音樂藝術家的目錄中包含充滿音樂文件的專輯目錄),但是您可以使用-maxdepth選項對其進行限制。

只查找當前目錄下的PNG文件(不包括子目錄):

  •  
$ find . -maxdepth 1 -name "*png"

要在當前目錄以及其他子目錄級別中查找和處理文件,請將最大深度增加1:

  •  
$ find . -maxdepth 2 -name "*png"

它的默認值是進入所有子目錄。

小延伸

使用循環的次數越多,節省的時間和精力就越多,可以處理的任務也就越大。您只是一個用戶,但是經過深思熟慮的循環,您可以使計算機完成艱苦的工作。

您可以並且應該像對待其他任何命令一樣對待循環,以便在需要對多個文件重複執行一個或兩個操作時可以將其放在手邊。但是,它也是進行認真編程的合法途徑,因此,如果您必須對任意數量的文件執行復雜的任務,請抽出一些時間來計劃工作流程。如果您可以在一個文件上實現目標,那麼將該可重複過程包裝在for循環中是相對簡單的,並且唯一需要的“編程”是瞭解變量的工作方式以及足夠的組織以將未處理的文件與已處理的文件分開。只需做一些練習,您就可以從一個Linux用戶轉移到知道如何編寫循環的Linux用戶!

 

Shell腳本關於循環的一些總結

不管是哪一門計算機語言,循環都是不可繞開的一個話題,Shell 當然也不是例外。下面總結一些 Shell 腳本里常用的循環相關的知識點,新手朋友可以參考。

for 循環

Shell 腳本里最簡單的循環當屬 for 循環,有編程基礎的朋友應該都有使用過 for 循環。最簡單的 for 循環如下所示,你只需將變量值依次寫在 in 後面即可:

#!/bin/bash

for num in 1 2 3 4
do
    echo $num
done

如果要循環的內容是字母表裏的連續字母或連續數字,那麼就可以按以下語法來寫腳本:

#!/bin/bash

for x in {a..z}
do
    echo $x
done

while 循環

除了 for 循環,Shell 同樣提供了 while 循環。對於其它語言,如果你見過 for 循環卻沒見過 while 循環,那麼你一定是學了個假語言。

在 while 循環裏,每進行一次循環,條件都會被判斷一次,來確定本次循環是否該繼續。其實在循環次數比較少的情況下,for 循環與 while 循環效果差不多,但如果循環次數比較多,比如 10 萬次,那麼 while 循環的優勢就體現出來了。

#!/bin/bash

n=1

while [ $n -le 4 ]
do
    echo $n
    ((n++))
done

循環套循環

像其它高級語言一樣,循環是可以互相嵌套的。比如下面這個例子,我們在 while 循環裏再套入一個 for 循環:

#!/bin/bash

n=1

while [ $n -lt 6 ]
do
    for l in {a..d}
    do
        echo $n$l
    done
    ((n++))
done

這個腳本執行的結果應該是 1a, 1b, 1c, 1d, 2a, 2b … 5d。

循環的內容是變化的

我們上面提到的 for 循環,循環變量要賦的值都列在了 in 後面的列表裏了。但這樣靈活性太差,因爲在很多情況下,循環變量要獲得的值是不固定的。

就比如,有個變量要獲得當前系統上所有用戶,但因爲每臺電腦用戶都不一樣,我們根本就沒辦法將這個變量寫死。

在這種情況下,我們可以使用 ls 命令將 /home 目錄下所有用戶都列出來,然後用循環變量依次獲取它們。完整代碼如下:

#!/bin/bash

for user in `ls /home`
do
    echo $user
done

當然,除了 ls ,Shell 還支持其它命令。比如我們可以使用 date 命令獲取當前系統時間,再依次打印出來:

$ for word in `date`
> do
>     echo $word
> done
Thu
Apr
9
08:12:09
CST
2020

變量值檢查

我們在使用 while 循環時,經常需要判斷一個變量的值是否大於或者小於某個數。有時候這個數也是用另一個變量來表示,那麼我們就需要判斷這個變量的值是否是數字。有三種判斷方法:

#!/bin/bash

echo -n "How many times should I say hello? "
read ans

if [ "$ans" -eq "$ans" ]; then
    echo ok1
fi

if [[ $ans = *[[:digit:]]* ]]; then
    echo ok2
fi

if [[ "$ans" =~ ^[0-9]+$ ]]; then
    echo ok3
fi

第一種方法看起來似乎是個廢話,但實際上,-eq 只能用於數值間判斷,如果是字符串則判斷不通過,所以這就保證了 ans 是個數值型變量。

第二種方法是直接使用 Shell 的通配符對變量進行判斷。

第三種方法就更直接了,使用正則表達式對變量進行判斷。

我們直接來看一個例子:

#!/bin/bash

echo -n "How many times should I say hello? "
read ans

if [ "$ans" -eq "$ans" ]; then
  n=1
  while [ $n -le $ans ]
  do
    echo hello
    ((n++))
  done
fi

在這個腳本里,我將要循環的次數傳入到 ans 變量,然後腳本就具體打印幾次 hello 。爲了保證我們傳入的內容是數字,我們使用了 if [ "$ans" -eq "$ans" ] 語句來判斷。如果我們傳入的不是數字,則不會進入 while 循環。

循環輸出文本文件內容

如果你想按行依次循環輸出文本文件的內容,可以這樣操作:

#!/bin/bash

echo -n "File> "
read file
n=0

while read line; do
  ((n++))
  echo "$n: $line"
done < $file

在這裏,我們使用 read 命令將文本文件的內容讀取存入 file 變量,然後再使用重定向(上述腳本最後一行)將 file 內容依次傳入 while 循環處理再打印出來。

死循環

有時候我們需要一直永遠循環做某件事,那麼我們就可以使用死循環。達到這個目的很簡單,只需使用while true 即可。

#!/bin/bash

while true
do
    echo -n "Still running at "
    date
    sleep 1
done

在以上這個腳本里,將每隔 1 秒打印一次 Still running at 具體時間 ,直到你按 Ctrl + C 終止這個腳本。

END

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章