簡體   English   中英

shell腳本中的命令替換沒有通配符

[英]Command substitution in shell script without globbing

考慮這個小shell腳本。

# Save the first command line argument
cmd="$1"

# Execute the command specified in the first command line argument
out=$($cmd)

# Do something with the output of the specified command
# Here we do a silly thing, like make the output all uppercase
echo "$out" | tr -s "a-z" "A-Z"

該腳本執行指定為第一個參數的命令,轉換從該命令獲得的輸出並將其打印到標准輸出。 可以以這種方式執行該腳本。

sh foo.sh "echo select * from table"

這不符合我的要求。 它可能會打印如下內容,

$ sh foo.sh "echo select * from table"
SELECT FILEA FILEB FILEC FROM TABLE

如果fileA,fileB和fileC存在於當前目錄中。

從用戶的角度來看,這個命令是合理的。 用戶在命令行參數中引用了* ,因此用戶不希望*被全局化。 但是我的腳本通過在命令替換中使用這個參數來使用戶感到驚訝,這會導致*全局變量,如上面的輸出中所示。

我希望輸出如下。

SELECT * FROM TABLE

cmd的整個文本實際上來自腳本的命令行參數,所以我想保留參數中存在的任何*符號而不用它們。

我正在尋找適用於任何POSIX shell的解決方案。

我提出的一個解決方案是在命令替換之前使用set -o noglob禁用globbing。 這是完整的代碼。

# Save the first command line argument
cmd="$1"

# Execute the command specified in the first command line argument
set -o noglob
out=$($cmd)

# Do something with the output of the specified command
# Here we do a silly thing, like make the output all uppercase
echo "$out" | tr -s "a-z" "A-Z"

這符合我的期望。

$ sh foo.sh "echo select * from table"
SELECT * FROM TABLE

除此之外,是否有任何其他概念或技巧(如引用機制)我需要注意在命令替換中禁用globbing而不必使用set -o noglob

我不反對set -o noglob 我只想知道是否還有其他辦法。 你知道,通過引用它們可以禁用通常命令行參數的globbing,所以我想知道命令替換是否有類似的東西。

如果我理解正確,您希望用戶提供一個shell命令作為命令行參數,該參數將由腳本執行,並且應該生成一個SQL字符串,該字符串將被處理(大寫)並回顯到標准輸出。

首先要說的是讓用戶提供一個shell命令是沒有意義的,腳本只是盲目地執行。 如果腳本在執行之前對命令進行了某種修改/預處理,那么它可能有意義,但如果沒有,那么用戶也可以自己執行命令並將輸出作為命令行傳遞給腳本參數,或通過標准輸入。

但話雖如此,如果你真的想這樣做,那么有兩件事需要說。 首先,這是使用的正確形式:

out=$(eval "$cmd");

需要對shell語法和擴展規則有相當深入的了解才能完全理解使用上述語法的基本原理,但基本上執行$cmd並執行eval "$cmd"產生微妙的差異,導致$cmd表單不適合執行給定shell命令字符串。

為了給出一些有希望澄清上述觀點的細節,在處理輸入時,shell按以下順序執行七種擴展 :(1)支撐擴展,(2)波浪擴展,(3)參數和變量擴展,(4)算術擴展,(5)命令替換,(6)字拆分,以及(7)路徑名擴展。 請注意, 變量擴展在該序列的中間發生,因此變量擴展的shell命令(由用戶提供)將不會獲得先前擴展類型的好處。 其他問題是前導變量賦值,管道和命令列表令牌將無法在$cmd表單下正確執行,因為它們在變量擴展之前(實際上在所有擴展之前)被解析和處理。

通過eval運行命令,正確地雙引號,您可以確保將完整的shell解析/處理/執行算法應用於腳本用戶提供的shell命令字符串。

第二點要說的是:如果您在腳本中嘗試上述正確的表單,您會發現它沒有解決您的問題。 您仍然可以將SELECT FILEA FILEB FILEC FROM TABLE作為輸出。

原因是:由於您已經決定要接受來自腳本用戶的任意shell命令,現在用戶有責任正確引用可能嵌入在該段代碼中的所有元字符。 接受shell命令作為命令行參數是沒有意義的,但是以某種方式更改了shell命令的處理規則,以便在執行給定的shell命令時某些元字符將不再是元字符。 實際上,您可以執行類似的操作,可能使用set -o noglob ,但是那時必須成為腳本和腳本用戶之間的契約; 必須讓用戶確切知道執行命令時的精確處理規則是什么,以便他可以正確使用該腳本。

在這種設計下,用戶可以按如下方式調用腳本(注意shell命令字符串評估的額外引用層;也可以反斜杠 - 只轉義星號):

$ sh foo.sh "echo 'select * from table'";

我想回到我之前關於整體設計的評論; 這樣做是沒有意義的。 采用文本到進程本身更有意義, 而不是預期產生文本到進程的shell命令。

以下是如何做到這一點:

## take the text-to-process via a command-line argument
sql="$1";

## process and echo it
echo "$sql"| tr a-z A-Z;

(我也刪除了tr-s選項,這在這里沒有意義。)

請注意,腳本現在更簡單,使用也更簡單:

$ sh foo.sh 'select * from table';

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM