rm 在命令行上有效,但在脚本中无效

rm 在命令行上有效,但在脚本中无效

当我rm *.old.*在命令行上执行时,它会正确删除,但是当我在脚本的以下部分执行时,它不会删除所有文件*.old.*

我的 Bash 脚本有什么问题:

 for i in ./*; do
    if [[ -f $i ]];  then

        if [[ $i  ==  *.old.* ]]; then
                oldfile=$i
                echo "this file is to be removed: $oldfile"
                rm $oldfile
                exec 2>errorfile
            if [ -s $errorfile ]
            then
                echo "rm failed"
            else
                echo "removed $oldfile!"
            fi
        else
            echo "file with old extension  does not exist"
        fi

        orig=$i
        dest=$i.old
        cp $orig $dest
        echo "Copied $i"

    else
        echo "${i} is not a file"
    fi 
done

答案1

脚本中存在各种可能的故障点。首先,rm *.old*将使用通配符创建所有匹配文件的列表,并且可以处理包含空格的文件名。但是,您的脚本为 glob 的每个结果分配一个变量,并且没有使用引号。如果您的文件名包含空格,这将中断。例如:

$ ls
'file name with spaces.old.txt'  file.old.txt
$ rm *.old.*   ## works: both files are deleted

$ touch "file.old.txt" "file name with spaces.old.txt"
$ for i in ./*; do oldfile=$i; rm -v $oldfile; done
rm: cannot remove './file': No such file or directory
rm: cannot remove 'name': No such file or directory
rm: cannot remove 'with': No such file or directory
rm: cannot remove 'spaces.old.txt': No such file or directory
removed './file.old.txt'

如您所见,文件名称中包含空格,循环失败。要正确执行此操作,您需要引用变量:

$ for i in ./*; do oldfile="$i"; rm -v "$oldfile"; done
removed './file name with spaces.old.txt'
removed './file.old.txt'

几乎每次$i在脚本中使用 时都会出现相同的问题。你应该总是引用你的变量

下一个可能的问题是,您似乎期望匹配*.old.*扩展名为 的文件.old。但事实并非如此。它匹配“0 个或更多字符”(*),然后是.,然后是“old”,然后是 another .,然后是“0 个或更多字符”。这意味着它将不是匹配类似的内容file.old,但仅限于“file.old.foo:

$ ls
file.old  file.old.foo
$ for i in *; do if [[ "$i" == *.old.* ]]; then echo $i; fi; done
file.old.foo     

所以,对手无能为力file.old。无论如何,你的脚本比需要的要复杂得多。试试这个:

#!/bin/bash

for i in *; do
    if [[ -f "$i" ]];  then
        if [[ "$i"  ==  *.old ]]; then
            rm -v "$i" || echo "rm failed for $i"
        else
            echo "$i doesn't have an .old extension"
        fi
        cp -v "$i" "$i".old
    else
        echo "$i is not a file"
    fi 
done

请注意,我添加了和 cp echo`-v语句。rmwhich does the same thing as what you were doing with your

这并不完美,因为当您找到时,例如,file.old它将被删除,稍后,脚本将尝试复制它并失败,因为文件不再存在。但是,您还没有解释您的脚本实际上试图做什么,所以除非您告诉我们您真正想要完成什么,否则我无法为您解决这个问题。

如果您想要的是 i) 删除所有带有.old扩展名的文件和 ii) 将.old扩展名添加到任何没有扩展名的现有文件,那么您真正需要的是:

#!/bin/bash

for i in *.old; do
    if [[ -f "$i" ]]; then
        rm -v "$i" || echo "rm failed for $i"
    else
        echo "$i is not a file"
    fi 
done
## All the ,old files have been removed at this point
## copy the rest
for i in *; do
    if [[ -f "$i" ]]; then
        ## the -v makes cp report copied files
        cp -v "$i" "$i".old
    fi
done

答案2

唯一rm $oldfile可能失败的情况是当您的文件名包含任何字符IFS(空格、制表符、换行符)或任何通配符(*?[])。

如果存在任何字符,IFSshell 将执行单词拆分,并根据变量扩展中通配符路径名扩展的存在进行扩展。

因此,例如,如果文件名是foo bar.old.,则变量oldfile将包含foo bar.old.

当你这样做时:

rm $oldfile

shell 首先将oldfile空格上的 的扩展拆分为两个单词foobar.old.。因此命令变为:

rm foo bar.old.

这显然会导致意想不到的结果。顺便说一句,如果扩展中有任何通配符(*, ?, []),那么路径名扩展也会完成。

您需要引用变量才能获得所需的结果:

rm "$oldfile"

现在,不会进行任何单词拆分或路径名扩展,因此您应该得到所需的结果,即所需的文件将被删除。如果任何文件名恰好以 开头-,则执行以下操作:

rm -- "$oldfile"

您可能会问,为什么在内部使用时不需要引用变量[[,原因是它[[是一个bash关键字,它在内部处理变量扩展并保持扩展文字。


现在,有几点:

  • 您应该在命令之前重定向 STDERR(exec 2>errorfilerm,否则[[ -s errorfile ]]测试会给出误报

  • 您使用了[ -s $errorfile ],您正在使用变量扩展$errorfile,由于errorfile变量未在任何地方定义,因此它将为 NUL。也许您的意思是,仅[ -s errorfile ]基于 STDERR 重定向

  • errorfile如果在使用时定义了变量[ -s $errorfile ],它会再次因上述IFS和通配符的情况而受阻,因为与 不同[[[不会由 内部处理bash

  • 在脚本的后半部分,您尝试删除cp已经删除的文件(再次不引用变量),这没有任何意义,您应该检查该夹头并根据您的目标进行必要的更正。

答案3

如果我理解你在做什么(删除任何带有.old后缀的文件,并复制任何带有后缀的现有文件.old),你可以使用 find 来代替:

#!/bin/sh

find . -maxdepth 1 -name \*.old -type f -printf "deleting %P\n" -delete
find . -maxdepth 1 -type f -printf "copying %P to %P.old\n" -exec cp '{}' '{}.old' \;

-maxdepth 0停止 find 命令在子目录中查找,-type f仅查找常规文件;-printf创建消息(%P是找到的文件名)。-exec cp调用复制函数,'{}'是文件名

相关内容