如何在 bash shell 脚本中正确处理通配符扩展?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/258211/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 17:47:14  来源:igfitidea点击:

How to properly handle wildcard expansion in a bash shell script?

linuxbashunixshell

提问by iankits

#!/bin/bash

hello()
{
    SRC=
    DEST=

    for IP in `cat /opt/ankit/configs/machine.configs` ; do
        echo $SRC | grep '*' > /dev/null
        if test `echo $?` -eq 0 ; then
            for STAR in $SRC ; do
                echo -en "$IP"
                echo -en "\n\t ARG1=$STAR ARG2=\n\n"
            done
        else
            echo -en "$IP"
            echo -en "\n\t ARG1=$SRC ARG2=$DEST\n\n"
        fi
    done
}

hello  

The above is the shell script which I provide source (SRC) & desitnation (DEST) path. It worked fine when I did not put in a SRC path with wild card ''. When I run this shell script and give ''.pdf or '*'as follows:

以上是我提供的源(SRC)和目标(DEST)路径的shell脚本。当我没有使用通配符“ 放入 SRC 路径时,它工作正常当我运行这个 shell 脚本并给出 ''.pdf 或 '*' 时,如下所示:

root@ankit1:~/as_prac# ./test.sh /home/dev/Examples/*.pdf /ankit_test/as

I get the following output:

我得到以下输出:

192.168.1.6
ARG1=/home/dev/Examples/case_Contact.pdf ARG2=/home/dev/Examples/case_howard_county_library.pdf

The DEST is /ankit_test/as but DEST also get manupulated due to '*'. The expected answer is

DEST 是 /ankit_test/as 但 DEST 也因“*”而被操纵。预期的答案是

ARG1=/home/dev/Examples/case_Contact.pdf ARG2=/ankit_test/as

So, if you understand what I am trying to do, please help me out to solve this BUG. I'll be grateful to you.

所以,如果你明白我在做什么,请帮我解决这个 BUG。我会感激你的。

Thanks in advance!!!

提前致谢!!!

I need to know exactly how I use '*.pdf' in my program one by one without disturbing DEST.

我需要确切地知道如何在我的程序中一一使用“*.pdf”而不打扰 DEST。

回答by dogbane

Your script needs more work. Even after escaping the wildcard, you won't get your expected answer. You will get:

您的脚本需要更多的工作。即使在转义通配符之后,您也不会得到预期的答案。你会得到:

ARG1=/home/dev/Examples/*.pdf ARG2=/ankit__test/as

Try the following instead:

请尝试以下操作:

for IP in `cat /opt/ankit/configs/machine.configs`
do
    for i in $SRC
    do
        echo -en "$IP"
        echo -en "\n\t ARG1=$i ARG2=$DEST\n\n"
    done
done

Run it like this:

像这样运行它:

root@ankit1:~/as_prac# ./test.sh "/home/dev/Examples/*.pdf" /ankit__test/as

回答by agnul

The shell will expand wildcards unless you escape them, so for example if you have

除非您转义通配符,否则外壳将展开通配符,例如,如果您有

$ ls
one.pdf two.pdf three.pdf

and run your script as

并运行您的脚本

./test.sh *.pdf /ankit__test/as

it will be the same as

它将与

./test.sh one.pdf two.pdf three.pdf /ankit__test/as

which is not what you expect. Doing

这不是您所期望的。正在做

./test.sh \*.pdf /ankit__test/as

should work.

应该管用。

回答by paxdiablo

If you can, change the order of the parameters passed to your shell script as follows:

如果可以,请更改传递给 shell 脚本的参数的顺序,如下所示:

./test.sh /ankit_test/as /home/dev/Examples/*.pdf

That would make your life a lot easier since the variable part moves to the end of the line. Then, the following script will do what you want:

这将使您的生活更轻松,因为可变部分移动到行尾。然后,以下脚本将执行您想要的操作:

#!/bin/bash
hello()
{
    SRC=
    DEST=

    for IP in `cat /opt/ankit/configs/machine.configs` ; do
        echo -en "$IP"
        echo -en "\n\t ARG1=$SRC ARG2=$DEST\n\n"
    done
}

arg2=
shift
while [[ "" != "" ]] ; do
        hello  $arg2
        shift
done

回答by godbyk

Here's what I came up with:

这是我想出的:

#!/bin/bash

hello()
{
    # DEST will contain the last argument
    eval DEST=$$#

    while [  != $DEST ]; do
        SRC=

        for IP in `cat /opt/ankit/configs/machine.configs`; do
            echo -en "$IP"
            echo -en "\n\t ARG1=$SRC ARG2=$DEST\n\n"
        done

        shift || break
    done
}

hello $*

Instead of passing only two parameters to the hello() function, we'll pass in all the arguments that the script got.

我们将传入脚本获得的所有参数,而不是仅将两个参数传递给 hello() 函数。

Inside the hello() function, we first assign the final argument to the DEST var. Then we loop through all of the arguments, assigning each one to SRC, and run whatever commands we want using the SRC and DEST arguments. Note that you may want to put quotation marks around $SRC and $DEST in case they contain spaces. We stop looping when SRC is the same as DEST because that means we've hit the final argument (the destination).

在 hello() 函数中,我们首先将最后一个参数分配给 DEST 变量。然后我们遍历所有参数,将每个参数分配给 SRC,并使用 SRC 和 DEST 参数运行我们想要的任何命令。请注意,您可能需要在 $SRC 和 $DEST 周围加上引号,以防它们包含空格。当 SRC 与 DEST 相同时,我们停止循环,因为这意味着我们已经命中了最后一个参数(目的地)。

回答by Alnitak

OK, this appears to do what you want:

好的,这似乎可以满足您的要求:

#!/bin/bash

hello() {

  SRC=
  DEST=

   while read IP ; do
     for FILE in $SRC; do
       echo -e "$IP"
       echo -e "\tARG1=$FILE ARG2=$DEST\n"
      done
   done < /tmp/machine.configs
 }

 hello "" 
  1. You still need to escape any wildcard characters when you invoke the script
  2. The double quotes are necessary when you invoke the hellofunction, otherwise the mere fact of evaluating $1causes the wildcard to be expanded, but we don't want that to happen until $SRCis assigned in the function
  1. 调用脚本时仍然需要转义任何通配符
  2. 调用hello函数时,双引号是必需的,否则仅仅是求值的事实$1会导致通配符被扩展,但我们不希望这种情况发生,直到$SRC在函数中赋值

回答by dogbane

You are also missing a final "done" to close your outer for loop.

您还缺少最后一个“完成”来关闭外部 for 循环。

回答by Mike L

For multiple input files using a wildcard such as *.txt, I found this to work perfectly, no escaping required. It should work just like a native bash app like "ls" or "rm." This was not documented just about anywhere so since I spent a better part of 3 days trying to figure it out I decided I should post it for future readers.

对于使用 *.txt 等通配符的多个输入文件,我发现它可以完美运行,无需转义。它应该像“ls”或“rm”这样的本机bash应用程序一样工作。这几乎没有在任何地方记录,所以因为我花了 3 天的大部分时间试图弄清楚它我决定我应该将它发布给未来的读者。

Directory contains the following files (output of ls)

目录包含以下文件(ls的输出)

file1.txt file2.txt file3.txt

Run script like

像这样运行脚本

$ ./script.sh *.txt

Or even like

甚至喜欢

$ ./script.sh file{1..3}.txt

The script

剧本

#!/bin/bash

# store default IFS, we need to temporarily change this
sfi=$IFS

#set IFS to $'\n\' - new line
IFS=$'\n'

if [[ -z $@ ]]
  then
  echo "Error: Missing required argument"
  echo
  exit 1
fi

# Put the file glob into an array
file=("$@")

# Now loop through them
for (( i=0 ; i < ${#file[*]} ; i++ ));
do

  if [ -w ${file[$i]} ]; then
     echo ${file[$i]} "  writable" 
  else
     echo ${file[$i]} " NOT writable"
  fi
done

# Reset IFS to its default value
IFS=$sfi

The output

输出

file1.txt writable
file2.txt writable
file3.txt writable

The key was switching the IFS (Internal Field Separator) temporarily. You have to be sure to store this before switching and then switch it back when you are done with it as demonstrated above.

关键是暂时切换 IFS(内部字段分隔符)。您必须确保在切换之前存储它,然后在完成后将其切换回,如上所示。

Now you have a list of expanded files (with spaces escaped) in the file[] array which you can then loop through. I like this solution the best, easiest to program for and easiest for the users.

现在您在 file[] 数组中有一个扩展文件列表(带有转义空格),然后您可以循环遍历该列表。我最喜欢这个解决方案,它是最好的、最容易编程的、对用户最简单的。

回答by Alnitak

There's no need to spawn a shell to look at the $?variable, you can evaluate it directly.

无需生成 shell 来查看$?变量,您可以直接对其进行评估。

It should just be:

它应该只是:

if [ $? -eq 0 ]; then

回答by dannysauer

You're running

你在跑步

./test.sh /home/dev/Examples/*.pdf /ankit_test/as 

and your interactive shell is expanding the wildcard before the script gets it. You just need to quote the first argument when you launch it, as in

并且您的交互式 shell 在脚本获取通配符之前正在扩展通配符。你只需要在启动它时引用第一个参数,如

./test.sh "/home/dev/Examples/*.pdf" /ankit_test/as

and then, in your script, quote "$SRC" anywhere where you literally want the things with wildcards (ie, when you do echo $SRC, instead use echo "$SRC") and leave it unquoted when you want the wildcards expanded. Basically, always put quotes around things which might contain shell metacharacters unless you want the metacharacters interpreted. :)

然后,在您的脚本中,在您真正想要使用通配符的任何地方引用“$SRC”(即,当您这样做时echo $SRC,而是使用echo "$SRC"),并在您想要扩展通配符时将其不加引号。基本上,除非您想要解释元字符,否则总是在可能包含 shell 元字符的内容周围加上引号。:)