HOWTO · Bash

在 Bash 中提取子字串

本教程演示了使用 cut 命令、子字串擴充套件和內部欄位分隔符在 bash 中提取子字串的不同方法。

本頁內容

在 Bash 中擷取字串的一部分時,應依輸入內容選擇合適的操作:cut 可選取由分隔符分開的欄位,Bash 子字串擴充會依位置選取字元,而 IFS 分割則會把分隔文字轉成可分別處理的欄位。這些方法彼此相關,但回傳資料的方式不同。

使用 cut 擷取由分隔符分開的欄位

當值有固定的分隔符,而你想取得其中某個欄位時,可以使用 cut。-d 選項用來設定分隔符,-f 則指定從 1 開始計數的欄位編號。在這個範例中,單字以底線分隔,因此第 3 個欄位是 three。請將變數展開加上引號,再傳給 printf;這樣可讓整個值維持為同一行輸入,也能避免使用不安全、未加引號的 echo $numbers 寫法。

執行此範例:

本節中的程式碼以及顯示的標籤或輸出仍為英文。

numbers='one_two_three_four_five'
substring=$(printf '%s\n' "$numbers" | cut -d '_' -f 3)
printf 'substring: %s\n' "$substring"
substring: three

cut 擷取的是欄位,而不是任意的字元範圍。若文字結構固定,且分隔符與欄位位置已知,這是合適的選擇;若要依位置取得一段字元,則應改用子字串擴充。

使用 Bash 子字串擴充擷取字元

Bash 提供的子字串擴充形式為 ${variable:offset:length}。位移從 0 開始,而長度代表要回傳的字元數。例如,在 one_two_three_four_five 中,位移 4 會從 two 的 t 開始,長度 3 則會選取這三個字元。原始字串中的所有字元,包括分隔符,都會計入位移。

執行此範例:

本節中的程式碼以及顯示的標籤或輸出仍為英文。

numbers='one_two_three_four_five'
substring=${numbers:4:3}
printf 'substring: %s\n' "$substring"
substring: two

當你知道字元的位置與範圍時,這是直接的做法。位移不是欄位編號:如果目標部分前面的文字長度改變,相同的位移可能會選到不同字元。若值是以分隔符切成具名或編號欄位,使用 cut 或 IFS 通常更容易因應變化。

使用 IFS 將字串分割成欄位

IFS 是 Bash 的內部欄位分隔符。它會控制未加引號的展開結果如何進行單字分割,也會影響內建的 read 如何分割輸入。暫時設定 IFS 值,可將以分隔符分開的字串拆成位置參數。$1、$2 和 $3 分別代表第一、第二和第三個參數;它們不是陣列索引語法。

此範例在函式中執行分割,並在使用未加引號的展開前停用路徑名稱展開。這點很重要,因為單字分割之後還會進行路徑名稱展開:如果欄位含有 *、? 或中括號模式,Shell 可能會將其替換成符合條件的檔名。對於分隔符已知且輸入受控的情況,可以使用這種寫法。若分隔符不是空白字元,讀取輸入至陣列時,可將 IFS 設為該分隔符並使用 read -a,之後使用欄位時則將陣列展開加上引號。預設的 IFS 會以空格、定位字元和換行分割欄位;將 IFS 改成某個分隔符後,輸入便會依該分隔符分割。這種方式無法保留欄位內含有指定分隔符的情況,而逐行讀取的 read -a 也無法將欄位內嵌的換行保留下來。處理這類格式時,應使用專為該格式設計的剖析器,不要只依賴 read -a。

執行此範例:

本節中的程式碼以及顯示的標籤或輸出仍為英文。

numbers='one_two_three_four_five'
extract_second() (
  local IFS=_
  set -f
  set -- $numbers
  printf 'second field: %s\n' "$2"
)
extract_second
second field: two

在函式內,set -- 會以分割後的欄位取代函式的位置參數;離開函式後,呼叫端的位置參數會恢復原狀。區域分隔符不會永久改變指令碼的 IFS。

選擇合適的方法

若文字使用已知的分隔符,例如以底線分隔的第三個值,請使用 cut 選取編號欄位。若要依已知的零起始位移與長度擷取字元,請使用 Bash 子字串擴充。若後續處理需要將欄位分別轉成 Shell 單字或位置參數,請使用 IFS 分割。

對於可預期的輸入,每種方法都很精簡。使用 cut 時,請將輸入加上引號;使用子字串擴充時,請使用大括號;也要記得,IFS 分割時若未將展開結果加上引號,可能接著觸發路徑名稱展開。如果分隔符也可能出現在資料中,或欄位內容可能含有 Shell 模式字元,請採用能將欄位保留為加上引號之陣列元素的剖析方式,不要依賴未加引號的單字分割。掌握這項差異,就能避免把欄位編號、字元位移和位置參數都當成可互換的「子字串」形式。