天天看點

如何從函數中傳回一個字元串(或大數、負數)?

如何從函數中傳回一個字元串(或大數、負數)?

知識點:shell中的return語句

如何從函數中傳回一個字元串(或大數、負數)?

在 Shell 中,函數的傳回值可以使用 return 指令來設定。return​ 指令用于将一個整數值作為函數的傳回值,并将流程控制傳回到函數的調用者處。以下是 return​ 指令的文法:

return [n]           

其中,n​ 是一個整數值,表示函數的傳回值。如果省略 n​,則預設傳回值為最後一條指令的退出狀态碼。在使用 return​ 指令時,可以将其作為函數中的任何一條指令來使用。

在函數傳回時,傳回值會被傳遞給函數的調用者。可以使用 $?​ 變量來擷取函數的傳回值,即 $?​ 變量會被設定為函數的傳回值。以下是一個示例:

function add() {
    local sum=$(($1 + $2))
    return $sum
}

add 2 3
echo "函數的傳回值為: $?"           

在這個示例中,add​ 函數接受兩個參數,計算它們的和,并使用 return​ 指令将和作為傳回值。然後,在函數調用完成後,使用 echo​ 指令輸出函數的傳回值,即 5。

需要注意的是,在 Shell 中,函數的傳回值隻能是一個整數值,而不能是其他類型的值。那如果需要函數傳回非0-255的數字時該怎麼辦?接下來将會分享常用的幾種方法。

方法1:捕獲标準輸出

你可以讓函數将資料寫入标準輸出(stdout),然後讓調用者捕獲标準輸出。

foo() {
   echo "this is my data"
}

x=$(foo)
printf 'foo returned "%s"\n' "$x"           

該方法的一個缺點是函數在子Shell中執行,這意味着在函數中進行的任何變量指派等操作不會在調用者的環境中生效(并且還會因為fork()而增加速度開銷)。這可能是一個問題,也可能不是一個問題,這取決于你的程式和函數的需求。另一個缺點是函數foo列印的所有内容都會被捕獲并放入變量中。如果foo還寫入了不打算作為傳回值的内容,則會導緻問題。為了将使用者提示和/或錯誤消息與“傳回”的資料隔離開來,請将它們重定向到不會被調用者捕獲的stderr。

foo() {
   echo "運作 foo()..."  >&2        # 将使用者提示和錯誤消息發送到 stderr
   echo "這是我的資料"              # 将在下面的變量中被指派
}

x=$(foo)                               # 輸出:運作 foo()...
printf 'foo 傳回了 "%s"\n' "$x"      # 輸出:foo 傳回了 "這是我的資料"           

方法2:全局變量

你可以将資料指派給全局變量,然後在調用者中引用這些變量。

foo() {
   return="this is my data"
}

foo
printf 'foo returned "%s"\n' "$return"           

與捕獲stdout相比,該方法的優點是函數不會在子Shell中執行,這意味着函數調用速度更快。它還意味着副作用(如其他變量指派和檔案描述符更改)會影響腳本的其餘部分。

該方法的缺點是,如果函數在子Shell中執行,則函數内部對全局變量的指派對調用者是不可見的。這意味着你無法在管道中使用該函數,例如。

方法3:寫入檔案

你的函數可以将資料寫入檔案,然後調用者可以從該檔案中讀取資料。

foo() {
   echo "這是我的資料" > "$1"
}

# 這不是處理臨時檔案的可靠代碼!
tmpfile=$(mktemp)   # GNU/Linux
foo "$tmpfile"
printf 'foo 傳回 "%s"\n' "$(<"$tmpfile")"
rm "$tmpfile"
# 如果這是一個真實的程式,應該會有錯誤檢查和陷阱。           

該方法的缺點顯而易見:你需要管理一個臨時檔案,這總是不友善的;必須有某個可寫的目錄,并且要有足夠的空間來存放其中的資料;等等。好處是,不管函數是否在子Shell中執行,它都能正常工作。

方法4:動态作用域變量

你可以使用作用域僅限于調用者和被調用函數的變量,而不是使用全局變量。

rand() {
   local max=$((32768 / $1 * $1))
   while (( (r=$RANDOM) >= max )); do :; done
   r=$(( r % $1 ))
}

foo() {
   local r
   rand 6
   echo "You rolled $((r+1))!"
}

foo
# 在全局作用域中,'r' 是不可見的。           

這種方法與使用全局變量的方法具有相同的優點和缺點,另外還有一個額外的優點,即全局變量命名空間不會被函數傳回變量所“污染”。

然而,這種技術在遞歸函數中不起作用。

# 這個例子無法工作。
fact() {
   local r      # 用于儲存我們調用的函數的傳回值
   if (($1 == 1)); then
      r=1       # 把資料發送回給調用者
   else
      fact $(($1 - 1))       # 遞歸調用自身
      r=$((r * $1))          # 把資料發送回給調用者
   fi
}           

存在變量名稱沖突-上面的示例嘗試同時使用r作為兩個沖突目的的變量。對于遞歸函數,請堅持使用全局變量技術。

# 這個例子是有效的。雖然不是計算階乘的最佳方法,但是它是一個簡單的遞歸函數示例。
fact() {
   if (($1 <= 1)); then
      r=1
   else
      fact "$(($1 - 1))"
      ((r *= $1))
   fi
}

fact 11
echo "$r"           

了解更多

如果您覺得文章内容對你有一點幫助可以關注我,我在頭條平台會持續分享更多實用的shell技巧和最佳實踐,如果想系統的快速學習shell的各種高階用法和生産環境避坑指南可以看看《shell腳本程式設計最佳實踐》專欄,專欄裡有更多的實用小技巧和腳本代碼分享。