簡體   English   中英

使用作為bash腳本參數傳遞的glob表達式

[英]Using a glob expression passed as a bash script argument

TL; DR:

為什么當myscript var=$1與使用var=foo*硬編碼的./myscript調用時,為什么不調用./myscript foo*


更長的形式

我在編寫的bash腳本中遇到了一個奇怪的問題。 我敢肯定有一個簡單的解釋,但我無法弄清楚。

我正在嘗試傳遞命令行參數,以在腳本中將其分配為變量。

我希望腳本允許2個命令行參數,如下所示:

$ bash my_bash_script.bash args1 args2

在我的腳本中,我分配了如下變量:

ARGS1=$1
ARGS2=$2

Args 1是要添加到輸出文件的字符串描述符。

Args 2是一組目錄:“ dir1,dir2,dir3”,我將其作為dir*傳遞

當我在腳本中將dir*分配給ARGS2時,它可以正常工作,但是當我將dir*作為第二個命令行參數傳遞時,它僅在dir*的通配符擴展中包括dir1

我認為這與外殼處理通配符(即使以args形式傳遞)的方式有關,但是我不太了解。

任何幫助,將不勝感激。


環境/用途

我有一組目錄:

dir_1_y_map, dir_1_x_map, dir_2_y_map, dir_2_x_map,
    ... dir_10_y_map, dir_10_x_map...

在這些目錄中,我嘗試通過*.status訪問擴展名為".status"的文件,並通過*report.txt訪問擴展名為".report.txt"

我想將dir_*_map作為第二個參數傳遞給腳本,並將其存儲在變量ARGS2中,然后使用它在每個目錄中搜索".status"".report"文件。

問題是從命令行傳遞dir_*_map不會給出目錄列表,而只是給出列表中的第一項。 如果我在腳本中分配變量ARGS2=dir_*_map ,則它將按我的ARGS2=dir_*_map工作。


解決方法:報價

事實證明,在引號中傳遞第二個參數允許通配符擴展適用於"dir_*_map"

#!/usr/bin/env bash
ARGS1=$1    
ARGS2=$2

touch $ARGS1".extension"

for i in /$ARGS2/*.status
do
    grep -e "string" $i >> $ARGS1".extension"
done

這是腳本的示例調用:

sh ~/path/to/script descriptor "dir_*_map"

我不完全理解何時/為什么必須在引號中傳遞某些參數,但我認為這與for循環中的通配符擴展有關。

解決“為什么”

就像var=foo*那樣,賦值不會擴展全局變量-也就是說,當您運行var=foo* ,文字字符串foo*被放入變量foo ,而不是與foo*匹配的文件列表中。

相比之下,在命令行上不加引號的foo*擴展了glob,將其替換為單個名稱列表,每個名稱均作為單獨的參數傳遞

因此,運行./yourscript foo*不會將foo*作為$1傳遞,除非不存在與該glob表達式匹配的文件。 相反,它變成類似於./yourscript foo01 foo02 foo03東西,每個參數都位於命令行的不同位置。

運行./yourscript "foo*"作為替代方法的原因是腳本內部未引用的擴展允許在以后擴展glob。 但是,這是一種不好的做法:全局擴展與字符串拆分同時發生(這意味着依靠此行為將使您無法傳遞包含在IFS找到的字符(通常為空格)的文件名),並且還意味着在以下情況下不能傳遞文字文件名:它們也可以解釋為glob(如果您有一個名為[1]的文件和一個名為1的文件,則傳遞[1]將始終替換為1 )。


習慣用法

建立這種慣用的方法是shift掉的第一個參數,然后疊代以后的,就像這樣:

#!/bin/bash
out_base=$1; shift

shopt -s nullglob                 # avoid generating an error if a directory has no .status

for dir; do                       # iterate over directories passed in $2, $3, etc
  for file in "$dir"/*.status; do # iterate over files ending in .status within those
      grep -e "string" "$file"    # match a single file
  done
done >"${out_base}.extension"

如果單個目錄中有多個.status文件,則可以使用find調用具有盡可能多參數的grep ,而不是逐個文件地單獨調用grep ,從而使所有這一切更有效。

#!/bin/bash
out_base=$1; shift

find "$@" -maxdepth 1 -type f -name '*.status' \
  -exec grep -h -- /dev/null '{}' + \
  >"${out_base}.extension"

上面的兩個腳本都希望傳遞的glob在調用shell上被引用。 因此,用法的形式為:

# being unquoted, this expands the glob into a series of separate arguments
your_script descriptor dir_*_map

這比將glob傳遞到腳本(然后將其擴展以檢索要使用的實際文件)要好得多。 它可以正確處理包含空格的文件名(其他做法則不這樣做)以及名稱本身就是glob表達式的文件。


其他注意事項:

  • 始終在擴展名兩邊加上雙引號! 否則,將導致附加的字符串拆分和全局擴展(按此順序)步驟。 如果遍歷(例如"$dir"/*.status 。status的情況),請在遍歷表達式開始之前結束引號。
  • for dir; do for dir; do等同for dir in "$@"; do for dir in "$@"; do ,它遍歷參數。 不要犯for dir in $*; do中使用for dir in $*; do的錯誤for dir in $*; do for dir in $*; dofor dir in $@; do for dir in $@; do吧! 后面的這些調用將列表的每個元素與IFS的第一個字符(默認情況下,該字符按順序包含空格,制表符和換行符)組合在一起,然后在其中找到的所有IFS字符上分割結果字符串,然后展開每個字符結果列表的組成部分作為一個整體。
  • /dev/null作為參數傳遞給grep是一種安全措施:確保單參數和多參數情況之間沒有不同的行為(例如, grep默認僅在傳遞時在輸出中打印文件名)多個參數),並確保您不會讓grep掛起嘗試從stdin讀取(如果它根本沒有傳遞任何其他文件名)(在這里find ,但xargs可以)。
  • 為自己的變量使用小寫名稱(與系統和外殼程序提供的變量全為大寫)不同,這符合POSIX指定的約定; 有關環境變量 ,請參見POSIX規范的第四段,請記住,環境變量和shell變量共享一個名稱空間。

暫無
暫無

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

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