在 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(非常規文件)之類的東西,我編寫的函數的行為將完全不同。

相關內容