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

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/null
  • 使用任何过滤器状工具谁可向刚刚通过,例如,将数据传递sed "" "$1" >"2"awk '1' "$1" >"$2"tr '.' '.' <"$1" >"$2"等。
  • 等等。

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

另一件要注意的事情是,我编写的函数的行为将cp与目标类似/dev/tty(非常规文件)的行为大不相同。