Bash

在 bash 中創建我自己的 cp 函式

  • May 23, 2017

對於一個作業,我被要求巧妙地編寫一個 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

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 [ ... ](需要空間 before 和 after[和 before ])。您也不應該嘗試將測試重定向到,/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"等。
  • 等等

棘手的一點是讓函式將元數據(所有權和權限)從源複製到目標。

cp另一件需要注意的事情是,如果目標是類似的東西/dev/tty(一個非正常文件),我編寫的函式的行為將完全不同。

引用自:https://unix.stackexchange.com/questions/366578