当我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
语句。rm
which 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
(空格、制表符、换行符)或任何通配符(*
,?
,[]
)。
如果存在任何字符,IFS
shell 将执行单词拆分,并根据变量扩展中通配符路径名扩展的存在进行扩展。
因此,例如,如果文件名是foo bar.old.
,则变量oldfile
将包含foo bar.old.
。
当你这样做时:
rm $oldfile
shell 首先将oldfile
空格上的 的扩展拆分为两个单词foo
和bar.old.
。因此命令变为:
rm foo bar.old.
这显然会导致意想不到的结果。顺便说一句,如果扩展中有任何通配符(*
, ?
, []
),那么路径名扩展也会完成。
您需要引用变量才能获得所需的结果:
rm "$oldfile"
现在,不会进行任何单词拆分或路径名扩展,因此您应该得到所需的结果,即所需的文件将被删除。如果任何文件名恰好以 开头-
,则执行以下操作:
rm -- "$oldfile"
您可能会问,为什么在内部使用时不需要引用变量[[
,原因是它[[
是一个bash
关键字,它在内部处理变量扩展并保持扩展文字。
现在,有几点:
您应该在命令之前重定向 STDERR(
exec 2>errorfile
)rm
,否则[[ -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
调用复制函数,'{}'
是文件名