cp
如果目标文件已经存在,该实用程序将很高兴地覆盖目标文件,而不会提示用户。
cp
无需使用即可实现基本功能的功能cp
是
cp () {
cat "$1" >"$2"
}
如果要在覆盖目标之前提示用户(请注意,如果非交互式shell调用该函数,则可能不希望这样做):
cp () {
if [ -e "$2" ]; then
printf '"%s" exists, overwrite (y/n): ' "$2" >&2
read
case "$REPLY" in
n*|N*) return ;;
esac
fi
cat "$1" >"$2"
}
诊断消息应转到标准错误流。这就是我要做的printf ... >&2
。
请注意,我们真的不需要rm
目标文件,因为重定向会截断它。如果我们确实确实想要rm
它,那么您就必须检查它是否是目录,如果是,则将目标文件放在该目录中,就像这样cp
做一样。这样做,但是仍然没有显式的rm
:
cp () {
target="$2"
if [ -d "$target" ]; then
target="$target/$1"
fi
if [ -d "$target" ]; then
printf '"%s": is a directory\n' "$target" >&2
return 1
fi
if [ -e "$target" ]; then
printf '"%s" exists, overwrite (y/n): ' "$target" >&2
read
case "$REPLY" in
n*|N*) return ;;
esac
fi
cat "$1" >"$target"
}
您可能还需要确保源确实存在,这cp
确实可以做到(cat
也可以这样做,因此当然可以将其完全排除在外,但是这样做会创建一个空的目标文件):
cp () {
if [ ! -f "$1" ]; then
printf '"%s": no such file\n' "$1" >&2
return 1
fi
target="$2"
if [ -d "$target" ]; then
target="$target/$1"
fi
if [ -d "$target" ]; then
printf '"%s": is a directory\n' "$target" >&2
return 1
fi
if [ -e "$target" ]; then
printf '"%s" exists, overwrite (y/n): ' "$target" >&2
read
case "$REPLY" in
n*|N*) return ;;
esac
fi
cat "$1" >"$target"
}
该函数不使用“ bashisms”,并且应该在所有类似sh
的shell中工作。
还有一些调整以支持多个源文件,还有一个-i
标志将在覆盖现有文件时激活交互式提示:
cp () {
local interactive=0
# Handle the optional -i flag
case "$1" in
-i) interactive=1
shift ;;
esac
# All command line arguments (not -i)
local -a argv=( "$@" )
# The target is at the end of argv, pull it off from there
local target="${argv[-1]}"
unset argv[-1]
# Get the source file names
local -a sources=( "${argv[@]}" )
for source in "${sources[@]}"; do
# Skip source files that do not exist
if [ ! -f "$source" ]; then
printf '"%s": no such file\n' "$source" >&2
continue
fi
local _target="$target"
if [ -d "$_target" ]; then
# Target is a directory, put file inside
_target="$_target/$source"
elif (( ${#sources[@]} > 1 )); then
# More than one source, target needs to be a directory
printf '"%s": not a directory\n' "$target" >&2
return 1
fi
if [ -d "$_target" ]; then
# Target can not be overwritten, is directory
printf '"%s": is a directory\n' "$_target" >&2
continue
fi
if [ "$source" -ef "$_target" ]; then
printf '"%s" and "%s" are the same file\n' "$source" "$_target" >&2
continue
fi
if [ -e "$_target" ] && (( interactive )); then
# Prompt user for overwriting target file
printf '"%s" exists, overwrite (y/n): ' "$_target" >&2
read
case "$REPLY" in
n*|N*) continue ;;
esac
fi
cat -- "$source" >"$_target"
done
}
您的代码中的if [ ... ]
空格不正确(,之前和之后[
以及之前需要空格]
)。您也不应尝试将测试重定向到,/dev/null
因为测试本身没有输出。此外,第一个测试应使用位置参数$2
,而不是字符串file
。
case ... esac
像我一样使用,可以避免使用来小写/大写来自用户的响应tr
。在中bash
,如果您仍然想这样做,则一种更便宜的方法是使用REPLY="${REPLY^^}"
(用于大写字母)或REPLY="${REPLY,,}"
(用于小写字母)。
如果用户对您的代码说“是”,则该函数会将目标文件的文件名放入目标文件中。这不是源文件的副本。它应该属于函数的实际复制位。
复制位是您使用管道实现的。管道用于将数据从一个命令的输出传递到另一命令的输入。这不是我们在这里需要做的事情。只需cat
在源文件上调用并将其输出重定向到目标文件即可。
您tr
早些时候打来的电话,也是一样。 read
将设置变量的值,但不产生任何输出,因此read
对任何内容进行管道传递都是毫无意义的。
除非用户说“ no”,否则不需要显式退出(否则该函数会遇到一些错误情况,如我的代码中的某些部分所示,但由于它是我使用的函数return
而不是exit
)。
另外,您说的是“功能”,但是您的实现是脚本。
请看一下https://www.shellcheck.net/,它是识别shell脚本有问题的好工具。
使用cat
只是一个复制文件的内容的方式。其他方式包括
dd if="$1" of="$2" 2>/dev/null
- 使用任何类似于过滤器的实用程序,例如,可以使它们仅通过
sed "" "$1" >"2"
或awk '1' "$1" >"$2"
或tr '.' '.' <"$1" >"$2"
等方式传递数据。
- 等等
棘手的一点是使函数将元数据(所有权和权限)从源复制到目标。
需要注意的另一件事是,我编写的函数的行为cp
与目标(/dev/tty
例如非常规文件)之类的行为有很大不同。