在 bash 中創建我自己的 cp 函式
對於一個作業,我被要求巧妙地編寫一個 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
源文件並將其輸出重定向到目標文件。你之前的電話也有同樣的問題
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
(一個非正常文件),我編寫的函式的行為將完全不同。