tim*_*y92 8 shell scripting bash shell-script files
对于一项作业,我被要求巧妙地编写一个 bash 函数,该函数具有与函数cp(复制)相同的基本功能。它只需要将一个文件复制到另一个文件,因此无需将多个文件复制到一个新目录。
由于我是 bash 语言的新手,我不明白为什么我的程序不起作用。原始函数要求覆盖已经存在的文件,所以我尝试实现它。它失败。
该文件似乎在多行失败,但最重要的是在检查要复制到的文件是否已存在 ( [-e "$2"]) 的条件下。即便如此,如果满足该条件(文件名...),它仍会显示应该触发的消息。
谁能帮我修复这个文件,可能为我对语言的基本理解提供一些有用的见解?代码如下。
#!/bin/sh
echo "file variable: $2"
if [-e file]&> /dev/null
then
echo "The file name already exists, want to overwrite? (yes/no)"
read | tr "[A-Z]" "[a-z]"
if [$REPLY -eq "yes"] ; then
rm "$2"
echo $2 > "$2"
exit 0
else
exit 1
fi
else
cat $1 | $2
exit 0
fi
Run Code Online (Sandbox Code Playgroud)
Kus*_*nda 26
cp如果该文件已经存在,该实用程序将很乐意覆盖目标文件,而不会提示用户。
实现基本cp功能的函数,而不使用cp将是
cp () {
cat "$1" >"$2"
}
Run Code Online (Sandbox Code Playgroud)
如果您想在覆盖目标之前提示用户(请注意,如果该函数是由非交互式 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"
}
Run Code Online (Sandbox Code Playgroud)
诊断消息应转到标准错误流。这就是我所做的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"
}
Run Code Online (Sandbox Code Playgroud)
您可能还想确保源确实存在,这是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"
}
Run Code Online (Sandbox Code Playgroud)
这个函数不使用“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
}
Run Code Online (Sandbox Code Playgroud)
您的代码中有错误的间距if [ ... ](前后需要空格[,以及之前])。您也不应该尝试将测试重定向到,/dev/null因为测试本身没有输出。此外,第一个测试应该使用位置参数$2,而不是字符串file。
case ... esac像我一样使用,您可以避免使用tr. 在 中bash,如果您无论如何都想这样做,那么更便宜的方法是使用REPLY="${REPLY^^}"(用于大写)或REPLY="${REPLY,,}"(用于小写)。
如果用户说“是”,则使用您的代码,该函数会将目标文件的文件名放入目标文件中。这不是源文件的副本。它应该贯穿到函数的实际复制位。
复制位是您使用管道实现的。管道用于将数据从一个命令的输出传递到另一个命令的输入。这不是我们需要在这里做的事情。只需调用cat源文件并将其输出重定向到目标文件。
同样的事情是错误的,你tr早点打电话。 read将设置变量的值,但不产生输出,因此管道read到任何东西都是无意义的。
除非用户说“不”,否则不需要显式退出(或者函数遇到一些错误情况,如我的代码位,但因为它是我使用的函数return而不是exit)。
另外,您说的是“功能”,但您的实现是一个脚本。
一定要看看https://www.shellcheck.net/,它是一个很好的工具,用于识别有问题的 shell 脚本。
使用cat只是一个复制文件的内容的方式。其他方式包括
dd if="$1" of="$2" 2>/dev/nullsed "" "$1" >"2"或awk '1' "$1" >"$2"或tr '.' '.' <"$1" >"$2"等。棘手的一点是让函数将元数据(所有权和权限)从源复制到目标。
另一件要注意的事情是,我编写的函数的行为将cp与目标类似/dev/tty(非常规文件)的行为大不相同。