我需要制作一个名为 的 shell 脚本chExt.sh
。接受文件扩展名,然后接受多个文件。然后需要更改文件扩展名或声明该文件不存在。
./chExt.sh txt ocelot.cpp ../otherFolder/file.H cat.dog.TXT king cobra.dat
是自动测试仪测试的总体思路。问题是我完全不知道如何处理名称中带有空格的文件。
我的代码是:
newExt="$1"
shift
for x in $*
do
fileName="$x"
if test -f "$fileName";
then
name=$(echo "$fileName" | rev | cut -f 2- -d '.' | rev)
newName="$name.$newExt"
if test "$newName" != "$fileName";
then
mv "$x" "$newName"
fi
else
echo "$fileName: No such file"
fi
done
答案1
如果不引用文件名,shell 无法知道这foo bar.txt
是一个参数而不是两个(foo
和bar.txt
)。因此,您需要像这样调用您的脚本:
./chExt.sh txt ocelot.cpp ../otherFolder/file.H cat.dog.TXT "king cobra.dat"
下一个问题是当变量扩展时,结果值在空白处分割(或者$IFS
变量设置的任何值)。因此,当您编写 时for i in $*
,$*
会在空格上展开并拆分。因此,对于一次迭代和下一次迭代来说,$i
变得如此。解决方法是引用要扩展的变量,以便它不会被拆分。king
cobra.txt
这给我们带来了下一个问题,即$*
和之间的区别$@
。如果使用"$*"
,参数将全部被视为一个长字符串:
$ cat foo.sh
#!/bin/sh
for i in "$*"; do
echo "$i"
done
$ foo.sh foo "bar baz"
foo bar baz
将以上内容与以下内容进行比较:
#!/bin/sh
for i in "$@"; do
echo "$i"
done
$ foo.sh foo "bar baz"
foo
bar baz
如您所见,使用"$@"
产生了预期的效果,每个输入参数都被单独处理,包括带空格的参数。
最后一点,您的脚本也会因以 开头的文件名-
以及与 选项对应的字母而阻塞(例如,echo
尝试使用名为 的文件)。-new
剥离扩展的更好方法是使用 shell 的本机字符串操作功能。从变量末尾${var%.*}
删除与 a 和 0 个或多个字符匹配的最短字符串。.
换句话说,就是扩展。然后,您还需要添加--
tomv
来指示选项的结束和参数的开始,以便它也可以处理以以下开头的文件名-
(请参阅指南 10这里)。
因此,脚本的改进和工作版本将是:
#!/bin/sh
newExt="$1"
shift
for fileName in "$@"
do
if test -f "$fileName";
then
# name=$(echo "$fileName" | rev | cut -f 2- -d '.' | rev)
name=${fileName%.*}
newName="$name.$newExt"
if test "$newName" != "$fileName";
then
mv -- "$fileName" "$newName"
fi
else
printf '%s: No such file\n' " $fileName"
fi
done
进一步阅读:
答案2
"$*"
和的另一个值得注意的特性"$@"
是它们代表 shell 数组 - shell 参数的当前集合。这些可以通过set
内置函数进行更改,但更普遍的是任何内置数组函数的默认操作数组 - 包括数组for
。
: "${2?no file arguments!}"
for x
do if [ "${2+:}" ]
then set ".$1"
else [ ".${x##*.}" != "$1" ] &&
[ -f "$x" ] &&
mv -- "$x" "${x%.*}$1"
fi
done
这与您的脚本的作用非常接近。它有点不同,因为它不会echo
对不是常规文件的参数执行操作。不过,我不确定你正在做你认为你在那里的事情:你正在检查你的论点是否是现有的、可访问的、常规文件,但是你正在echo
向 stdout 发送一条消息,上面写着参数:没有这样的文件。在类 Unix 系统上,一切都是文件:管道是文件,目录是文件,设备是文件。所以你的测试和你的信息不一致。更重要的是,如果您要求mv
移动一个不存在的文件,它会为您打印您的消息,并打印到 stderr - 全部自行完成。
我注意到的另一件事是,您正在测试您的新名称是否等于您的旧名称,我认为这是为了避免mv
抱怨将文件移动到自身之上。碰巧的是,mv
有一个选项可以让这种情况安静下来。
我想我会做你的事情,比如:
: "${2?no file arguments!}"
for x
do [ "${2+:}" ] &&
set -- ".$1" && continue
set -- "${x%.*}$1" "$1"
{ [ -e "$1" ] && printf "%s: exists!\n" "$1"; } ||
{ ! [ -f "$x" ] && printf "%s: not a regular file.\n" "$x"; } ||
mv -f -- "$x" "$1"
shift
done >&2