在 bash 中创建我自己的 cp 函数

在 bash 中创建我自己的 cp 函数

cp对于一项作业,我被要求巧妙地编写一个 bash 函数,该函数具有与函数(复制)相同的基本功能。它只需将一个文件复制到另一个文件,因此无需将多个文件复制到新目录。

由于我是 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

答案1

如果目标文件已经存在,该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 do (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^^}"(for uppercasing) 或REPLY="${REPLY,,}"(for lowercasing)。

如果用户对您的代码说“是”,则该函数会将目标文件的文件名放入目标文件中。这不是源文件的副本。它应该落到函数的实际复制位。

复制位是您使用管道实现的。管道用于将数据从一个命令的输出传递到另一命令的输入。这不是我们需要在这里做的事情。只需调用cat源文件并将其输出重定向到目标文件即可。

你之前打电话也有同样的问题trread将设置变量的值,但不产生输出,因此通过管道传输read任何内容都是无意义的。

除非用户说“不”,否则不需要显式退出(或者该函数遇到一些错误情况,如我的代码位中所示,但因为它是我使用的函数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"
  • ETC。

棘手的一点是让函数将元数据(所有权和权限)从源复制到目标。

cp另一件需要注意的事情是,如果目标是/dev/tty(非常规文件)之类的东西,我编写的函数的行为将完全不同。

相关内容