[英]find command works on prompt, not in bash script - pass multiple arguments by variable
我搜索了類似問題的問題,但沒有找到一個適合我情況的問題。
下面是一個非常簡短的腳本,演示了我所面臨的問題:
#!/bin/bash
includeString="-wholename './public_html/*' -o -wholename './config/*'"
find . \( $includeString \) -type f -mtime -7 -print
基本上,我們需要在一個文件夾內進行搜索,但只能在其某些子文件夾中進行搜索。 在我的較長腳本中,includeString是從數組構建的。 對於此演示,我保持簡單。
基本上,當我運行腳本時,它什么也找不到。 沒有錯誤,也沒有命中。 如果我手動運行find命令,它將起作用。 如果我刪除了($ includeString),它也可以工作,盡管顯然不會將其自身限制在我想要的文件夾中。
那么,為什么相同的命令可以從命令行運行而不能從bash腳本運行呢? 以這種方式傳遞$ includeString導致失敗的原因是什么?
您遇到了外殼程序如何處理變量擴展的問題。 在腳本中:
includeString="-wholename './public_html/*' -o -wholename './config/*'"
find . \( $includeString \) -type f -mtime -7 -print
這將導致在-wholename
匹配文字字符串 './public_html/*'
文件中find
文件。 即,包含單引號的文件名 。 由於您的路徑中沒有空格,因此,最簡單的解決方案是刪除單引號:
includeString="-wholename ./public_html/* -o -wholename ./config/*"
find . \( $includeString \) -type f -mtime -7 -print
不幸的是,您可能會在這里被通配符擴展所咬(外殼會在find
通配符之前嘗試擴展通配符)。
但是,正如伊坦(Etan)在評論中指出的那樣,這似乎是不必要的復雜。 您可以簡單地執行以下操作:
find ./public_html ./config -type f -mtime -7 -print
如果要存儲參數列表並在以后展開,則使用此參數的正確形式是數組,而不是字符串:
includeArgs=( -wholename './public_html/*' -o -wholename './config/*' )
find . '(' "${includeArgs[@]}" ')' -type f -mtime -7 -print
BashFAQ#50中對此進行了詳細介紹。
注意:正如Etan在評論中指出的那樣,在這種情況下,更好的解決方案可能是重新編寫find
命令,但是一般通過變量傳遞多個參數是一種值得探索的技術。
tl; dr :
問題不是特定於find
,而是Shell如何解析命令行的問題 。
嵌入在變量值中的引號字符被視為文字 :它們既不被識別為參數邊界定界符,也不在解析后將其刪除 ,因此, 不能將嵌入引號的字符串變量與 直接用作參數的一部分的傳遞給多個參數一起 使用命令 。
為了可靠地傳遞存儲在變量中的多個參數 ,
bash
, ksh
, zsh
)-參見下文。 xargs
參見下文。 強大的解決方案 :
注意:這些解決方案假定存在以下腳本 ,我們稱其為echoArgs
,該腳本以診斷形式打印傳遞給它的參數:
#!/usr/bin/env bash
for arg; do # loop over all arguments
echo "[$arg]" # print each argument enclosed in [] so as to see its boundaries
done
此外,假定將執行以下命令的等效項 :
echoArgs one 'two three' '*' last # note the *literal* '*' - no globbing
除了最后一個 變量以外的所有參數 。
因此,預期結果是:
[one]
[two three]
[*]
[last]
bash
, ksh
, zsh
): # Assign the arguments to *individual elements* of *array* args.
# The resulting array looks like this: [0]="one" [1]="two three" [2]="*"
args=( one 'two three' '*' )
# Safely pass these arguments - note the need to *double-quote* the array reference:
echoArgs "${args[@]}" last
xargs
兼容POSIX的替代方法: POSIX實用xargs
,不像外殼本身, 能夠嵌入在一個字符串認可引號字符串:
# Store the arguments as *single string* with *embedded quoting*.
args="one 'two three' '*'"
# Let *xargs* parse the embedded quoted strings correctly.
# Note the need to double-quote $args.
echo "$args" | xargs -J {} echoArgs {} last
請注意, {}
是一個自由選擇的占位符,它允許您控制xargs
提供的參數在結果命令行中的位置。
如果所有xarg
參數都xarg
在最后 ,則根本不需要使用-J
。
為了完整起見: eval
也可以用於解析嵌入在另一個字符串中的帶引號的字符串,但是eval
存在安全風險:任意命令最終可能會被執行; 鑒於以上討論的安全解決方案,因此無需使用eval
。
最后,查爾斯·達菲(Charles Duffy)在注釋中提到了另一種安全的替代方法,但是,它需要更多的編碼:將要調用的命令封裝在shell函數中 ,將變量參數作為單獨的參數傳遞給該函數,然后操縱所有參數數組$@
在函數內部以補充固定參數(使用set
),並使用"$@"
調用命令。
有關shell的字符串處理問題的說明:
將字符串分配給變量時 , 嵌入的引號字符將成為字符串的一部分 :
var='one "two three" *'
$var
現在從字面上包含one "two three" *
,即以下4個單詞(而不是預期的3個單詞),每個單詞之間用空格分隔:
one
"two
- "
是單詞本身的一部分! three"
- "
是單詞本身的一部分! *
當您使用$var
引號作為參數列表的一部分時, 上述分解成4個單詞的過程正是Shell 最初要做的事情 -這個過程稱為單詞拆分 。 請注意,如果要對變量引用( "$var"
) 雙引號 ,則整個字符串將始終成為單個參數。
$var
被擴展為其值,即所謂的參數擴展之一 , 因此外殼程序不會嘗試將該值內的嵌入引號識別為標記參數邊界 -這僅適用於按字面指定的引號字符,這是該變量的直接部分。命令行(假設這些引號字符本身沒有被引號)。 但是,shell 還會對結果的4個單詞應用路徑名擴展 (globbing),因此,碰巧與文件名匹配的任何單詞都將擴展為匹配的文件名。
簡而言之: $var
值中的引號字符既不識別為參數邊界定界符,也不在解析后將其刪除 。 此外, $var
值中的單詞會受到路徑名擴展的影響 。
這意味着傳遞多個參數的唯一方法是在變量值內不加引號 (並且對變量的引用也不要加引號 ),這是:
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.