bash rm 无法通过通配符从脚本中删除文件,但可以在 shell 提示符下工作

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/790195/
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 18:07:54  来源:igfitidea点击:

rm fails to delete files by wildcard from a script, but works from a shell prompt

linuxbashshell

提问by EMP

I've run into a really silly problem with a Linux shell script. I want to delete all files with the extension ".bz2" in a directory. In the script I call

我在使用 Linux shell 脚本时遇到了一个非常愚蠢的问题。我想删除目录中所有扩展名为“.bz2”的文件。在我调用的脚本中

rm "$archivedir/*.bz2"

where $archivedir is a directory path. Should be pretty simple, shouldn't it? Somehow, it manages to fail with this error:

其中 $archivedir 是目录路径。应该很简单,不是吗?不知何故,它设法失败并出现此错误:

rm: cannot remove `/var/archives/monthly/April/*.bz2': No such file or directory

But there isa file in that directory called test.bz2 and if I change my script to

但是,在该目录名为test.bz2一个文件,如果我改变我的脚本

echo rm "$archivedir/*.bz2"

and copy/paste the output of that line into a terminal window the file is removed successfully. What am I doing wrong?

并将该行的输出复制/粘贴到终端窗口中,文件已成功删除。我究竟做错了什么?

回答by Doug

TL;DR

TL; 博士

Quote only the variable, not the whole expected path with the wildcard

仅引用变量,而不是使用通配符引用整个预期路径

rm "$archivedir"/*.bz2

Explanation

解释

  • In Unix, programs generally do not interpret wildcards themselves. The shell interprets unquoted wildcards, and replaces each wildcard argument with a list of matching file names. if $archivedir might contain spaces, then rm $archivedir/*.bz2might not do what you

  • You can disable this process by quoting the wildcard character, using double or single quotes, or a backslash before it. However, that's not what you want here - you do want the wildcard expanded to the list of files that it matches.

  • Be careful about writing rm $archivedir/*.bz2(without quotes). The word splitting (i.e., breaking the command line up into arguments) happens after$archivedir is substituted. So if $archivedir contains spaces, then you'll get extra arguments that you weren't intending. Say archivedir is /var/archives/monthly/April to June. Then you'll get the equivalent of writing rm /var/archives/monthly/April to June/*.bz2, which tries to delete the files "/var/archives/monthly/April", "to", and all files matching "June/*.bz2", which isn't what you want.

  • 在 Unix 中,程序本身通常不解释通配符。shell 解释不带引号的通配符,并用匹配文件名列表替换每个通配符参数。如果 $archivedir 可能包含空格,则rm $archivedir/*.bz2可能不会执行您的操作

  • 您可以通过引用通配符、使用双引号或单引号或在它之前使用反斜杠来禁用此过程。但是,这不是您想要的 - 您确实希望将通配符扩展到它匹配的文件列表。

  • 书写时要小心rm $archivedir/*.bz2(不带引号)。单词拆分(即,将命令行分解为参数)发生$archivedir 被替换之后。因此,如果 $archivedir 包含空格,那么您将获得您不想要的额外参数。说 archivedir 是/var/archives/monthly/April to June. 然后你会得到相当于writing rm /var/archives/monthly/April to June/*.bz2,它试图删除文件“/var/archives/monthly/April”、“to”和所有匹配“June/*.bz2”的文件,这不是你想要的.

The correct solution is to write:

正确的解决方法是这样写:

rm "$archivedir"/*.bz2

回答by Avery Payne

Your original line

你原来的线路

rm "$archivedir/*.bz2"

Can be re-written as

可以重写为

rm "$archivedir"/*.bz2

to achieve the same effect. The wildcard expansion is not taking place properly in your existing setup. By shifting the double-quote to the "front" of the file path (which is legitimate) you avoid this.

达到同样的效果。通配符扩展在您现有的设置中没有正确进行。通过将双引号移到文件路径的“前面”(这是合法的),您可以避免这种情况。

回答by Charlie Martin

Just to expand on this a bit, bash has fairly complicated rules for dealing with metacharacters in quotes. In general

只是稍微扩展一下,bash 有相当复杂的规则来处理引号中的元字符。一般来说

  • almost nothing is interpreted in single-quotes:

     echo '$foo/*.c'                  => $foo/*.c
     echo '\*'                       => \*
    
  • shell substitution is done inside double quotes, but file metacharacters aren't expanded:

     FOO=hello; echo "$foo/*.c"       => hello/*.c
    
  • everything inside backquotes is passed to the subshell which interprets them. A shell variable that is not exported doesn't get defined in the subshell. So, the first command echoes blank, but the second and third echo "bye":

    BAR=bye echo `echo $BAR`
    BAR=bye; echo `echo $BAR`
    export BAR=bye; echo `echo $BAR`
    
  • 几乎没有什么是用单引号解释的:

     echo '$foo/*.c'                  => $foo/*.c
     echo '\*'                       => \*
    
  • shell 替换在双引号内完成,但文件元字符不会扩展:

     FOO=hello; echo "$foo/*.c"       => hello/*.c
    
  • 反引号内的所有内容都传递给解释它们的子shell。未导出的 shell 变量不会在子 shell 中定义。因此,第一个命令回显空白,但第二个和第三个命令回显“再见”:

    BAR=bye echo `echo $BAR`
    BAR=bye; echo `echo $BAR`
    export BAR=bye; echo `echo $BAR`
    

(And getting this to print the way you want it in SO takes several triesis apparently impossible...)

(并且让它以您想要的方式打印,所以需要多次尝试显然是不可能的......)

回答by Brian Gianforcaro

The quotes are causing the string to be interpreted as a string literal, try removing them.

引号导致字符串被解释为字符串文字,请尝试删除它们。

回答by Christo

I've seen similar errors when calling a shell script like ./shell_script.shfrom another shell script. This can be fixed by invoking it as sh shell_script.sh

从另一个 shell 脚本调用./shell_script.sh这样的 shell 脚本时,我看到过类似的错误 。这可以通过将其调用为 sh shell_script.sh 来修复

回答by holms

Why not just rm -rf */*.bz2? Works for me on OSX.

为什么不只是rm -rf */*.bz2?在 OSX 上对我有用。