当我rm *.old.*
在命令行上执行它时,它会正确删除,但是当我在脚本的以下部分执行它时,它并没有 rm 所有*.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
Run Code Online (Sandbox Code Playgroud)
ter*_*don 14
您的脚本中有各种可能的故障点。首先,rm *.old*
将使用globbing创建所有匹配文件的列表,并且可以处理包含空格的文件名。但是,您的脚本会为 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'
Run Code Online (Sandbox Code Playgroud)
如您所见,对于名称中包含空格的文件,循环失败。要正确执行此操作,您需要引用变量:
$ for i in ./*; do oldfile="$i"; rm -v "$oldfile"; done
removed './file name with spaces.old.txt'
removed './file.old.txt'
Run Code Online (Sandbox Code Playgroud)
同样的问题几乎适用于 $i
于脚本中的。你应该总是引用你的变量。
下一个可能的问题是您似乎希望*.old.*
匹配扩展名为.old
. 它没有。它匹配“0 个或更多字符”( *
),然后是 a .
,然后是“旧”,然后是另一个.
,然后是“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
Run Code Online (Sandbox Code Playgroud)
所以,没有对手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
Run Code Online (Sandbox Code Playgroud)
请注意,我添加了 -v
了rm
和 cp which does the same thing as what you were doing with your
echo` 语句。
这并不完美,因为当您发现,例如,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
Run Code Online (Sandbox Code Playgroud)
唯一rm $oldfile
可能失败的情况是您的文件名包含任何字符IFS
(空格、制表符、换行符)或任何通配符(*
,?
, []
)。
如果任何字符 IFS
shell 将执行分词,并基于变量扩展上的通配符路径名扩展的存在。
因此,例如,如果文件名是foo bar.old.
,变量oldfile
将包含foo bar.old.
.
当你这样做时:
rm $oldfile
Run Code Online (Sandbox Code Playgroud)
shell 首先将oldfile
on 空间的扩展拆分为两个词,foo
和bar.old.
. 所以命令变成:
rm foo bar.old.
Run Code Online (Sandbox Code Playgroud)
这显然会导致意想不到的结果。顺便说一句,如果您有任何通配符 ( *
,?
[]
在扩展中 , ),那么路径名扩展也会被完成。
您需要引用变量以获得所需的结果:
rm "$oldfile"
Run Code Online (Sandbox Code Playgroud)
现在,不会进行分词或路径名扩展,因此您应该得到所需的结果,即所需的文件将被删除。如果任何文件名碰巧以 开头-
,请执行以下操作:
rm -- "$oldfile"
Run Code Online (Sandbox Code Playgroud)
您可能会问,为什么我们在使用 inside 时不需要引用变量[[
,原因[[
是一个bash
关键字,它在内部处理变量扩展并保持扩展文字。
现在,几点:
您应该exec 2>errorfile
在rm
命令之前重定向 STDERR ( )否则[[ -s errorfile ]]
测试会产生误报
您已经使用过[ -s $errorfile ]
,您正在使用变量扩展$errorfile
,如果errorfile
变量未在任何地方定义,那么这将是 NUL 。也许你的意思是,只是[ -s errorfile ]
基于 STDERR 重定向
如果errorfile
定义了变量,在使用 时[ -s $errorfile ]
,它会再次阻塞在上面提到的IFS
和 globbing 的情况下,因为不一样[[
,[
不在内部处理bash
在脚本的后面部分,您正在尝试cp
删除已删除的文件(再次不引用变量),这没有任何意义,您应该检查该卡盘并根据您的目标进行必要的更正。
如果我了解您在做什么(删除带有.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' \;
Run Code Online (Sandbox Code Playgroud)
-maxdepth 0
停止 find 命令在子目录中查找,-type f
仅查找常规文件;-printf
创建消息(%P
是找到的文件名)。调用-exec cp
复制函数并且'{}'
是文件名