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

3
#!/bin/bash

hello()
{
    SRC=$1
    DEST=$2

    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=$2\n\n"
            done
        else
            echo -en "$IP"
            echo -en "\n\t ARG1=$SRC ARG2=$DEST\n\n"
        fi
    done
}

hello $1 $2

以上是我提供的shell脚本,其中包含源(SRC)和目标(DEST)路径。当我没有使用通配符“”来指定SRC路径时,它可以正常工作。但是当我运行这个shell脚本并使用“”.pdf或“*”时,它会出现以下问题:

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

我收到以下输出:
192.168.1.6
ARG1=/home/dev/Examples/case_Contact.pdf ARG2=/home/dev/Examples/case_howard_county_library.pdf

DEST 是 /ankit_test/as,但是由于 "*" 的作用,DEST 也会被操纵。预期的答案是:
ARG1=/home/dev/Examples/case_Contact.pdf ARG2=/ankit_test/as

如果您理解我的意图,请帮我解决这个BUG。非常感谢!

提前感谢您!

我需要准确了解如何在程序中逐个使用“*.pdf”,而不会影响DEST。

9个回答

5

如果不对通配符进行转义,shell会自动展开它们。例如,如果你有以下命令:

```bash ls *.txt ```

那么shell会自动把所有以".txt"结尾的文件名展开。如果你想使用真正的通配符而不是展开后的结果,可以用反斜杠(\)来转义。

$ ls
one.pdf two.pdf three.pdf

并运行您的脚本为

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

它将和之前的一样

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

这并不是您所期望的。进行操作

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

应该可以工作。


你说得对,但我想一个接一个地使用所有的 'one.pdf, two.pdf, three.pdf'。例如,我需要将它们都复制到另一个位置。那么解决方案是什么?请帮帮我。 - Ankit Singh

4

您的脚本需要更多的工作。即使通配符被转义,您也无法得到预期的答案。您将会得到:

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

请尝试以下方法:
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

按照以下方式运行:

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

4

如果可以的话,请按照以下方式更改传递给您的Shell脚本参数的顺序:

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

如果变量部分移到行末,那将使你的生活变得更加轻松。接下来的脚本将实现你想要的功能:

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

    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=$1
shift
while [[ "$1" != "" ]] ; do
        hello $1 $arg2
        shift
done

对我来说,这似乎是最直观的答案:不需要转义通配符,并且在第一个参数上简单循环。 - Bert Zangle

1

以下是我想出来的:

#!/bin/bash

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

    while [ $1 != $DEST ]; do
        SRC=$1

        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 $*

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

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


1

好的,这似乎可以实现你想要的功能:

#!/bin/bash

hello() {

  SRC=$1
  DEST=$2

   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" $2
  1. 当您调用脚本时,仍需要转义任何通配符字符。
  2. 在调用hello函数时,双引号是必需的,否则仅评估$1的事实会导致通配符被扩展,但我们不希望在$SRC分配给函数之前发生这种情况。

这个版本只使用shell内置命令,因此不需要生成任何子进程来完成其工作。 - Alnitak

1

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

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

file1.txt file2.txt file3.txt

运行脚本如下

$ ./script.sh *.txt

甚至可以像这样

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

脚本

#!/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

输出

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

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

现在您有一个文件列表(已转义空格)在文件[]数组中,您可以对其进行循环。我最喜欢这个解决方案,编程最简单,用户使用也最容易。


1
你还缺少一个最后的"done"来关闭你的外部for循环。

0

没有必要生成一个 shell 来查看 $? 变量,您可以直接评估它。

应该只是:

if [ $? -eq 0 ]; then

我在shell脚本方面没有太多经验,所以不知道这是否是一种奇怪的使用方式。我也尝试了你的方法,但是出现了以下错误。在使用时出现以下错误./test.sh: line 14: [: missing `]' - Ankit Singh

0

你正在运行

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

你的交互式 shell 在脚本获取通配符之前就已经展开了它。你只需要在启动时引用第一个参数,例如:

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

然后,在您的脚本中,引用"$SRC",任何地方都可以使用通配符(即,当您执行“echo $SRC”时,改为使用“echo”$SRC"“)并在想要扩展通配符时将其保持未引用。 基本上,除非您希望解释元字符,否则始终在可能包含shell元字符的内容周围放置引号。 :)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接