簡體   English   中英

find命令在提示符下起作用,而不是在bash腳本中起作用-通過變量傳遞多個參數

[英]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如何解析命令行的問題

  • 嵌入在變量值中的引號字符視為文字 :它們既不被識別為參數邊界定界符,也不在解析后將其刪除 ,因此, 不能將嵌入引號的字符串變量與 直接用作參數的一部分的傳遞給多個參數一起 使用命令

  • 為了可靠地傳遞存儲在變量中的多個參數

    • 在支持它們的shell中使用數組變量bashkshzsh )-參見下文。
    • 否則, 為了符合POSIX,請使用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]
  • 使用數組變量( bashkshzsh ):
# 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只會刪除這些直接指定的引號字符-這個過程稱為quote remove
  • 但是,shell 還會對結果的4個單詞應用路徑名擴展 (globbing),因此,碰巧與文件名匹配的任何單詞都將擴展為匹配的文件名。

  • 簡而言之: $var值中的引號字符既不識別為參數邊界定界符,也不在解析后將其刪除 此外, $var值中的單詞會受到路徑名擴展的影響

  • 這意味着傳遞多個參數的唯一方法是在變量值內不加引號 (並且對變量的引用也不要加引號 ),這是:

    • 不適用於帶有嵌入式空格或外殼元字符的值
    • 始終使值經受路徑名擴展

暫無
暫無

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

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