Các cp
tiện ích hạnh phúc sẽ ghi đè lên tập tin mục tiêu nếu tập tin đã tồn tại, mà không nhắc người sử dụng.
Một chức năng thực hiện cp
khả năng cơ bản , mà không sử dụng cp
sẽ là
cp () {
cat "$1" >"$2"
}
Nếu bạn muốn nhắc người dùng trước khi ghi đè mục tiêu (lưu ý rằng có thể không mong muốn thực hiện việc này nếu chức năng được gọi bởi trình bao không tương tác):
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"
}
Các thông báo chẩn đoán sẽ đi đến luồng lỗi tiêu chuẩn. Đây là những gì tôi làm với printf ... >&2
.
Lưu ý rằng chúng ta không thực sự cần đến rm
tệp đích vì chuyển hướng sẽ cắt bớt nó. Nếu chúng tôi thực sự muốn rm
nó trước tiên, thì bạn sẽ phải kiểm tra xem đó có phải là một thư mục hay không và nếu có, hãy đặt tệp mục tiêu vào trong thư mục đó thay vào đó, chỉ là cách cp
sẽ làm. Điều này đang làm điều đó, nhưng vẫn không rõ ràng 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"
}
Bạn cũng có thể muốn đảm bảo rằng nguồn thực sự tồn tại, đó là điều cp
cần làm ( cat
cũng vậy, vì vậy nó có thể bị bỏ hoàn toàn, tất nhiên, nhưng làm như vậy sẽ tạo ra một tệp mục tiêu trống):
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"
}
Hàm này không sử dụng "bashism" và sẽ hoạt động trong các sh
shell giống như.
Với một chút tinh chỉnh để hỗ trợ nhiều tệp nguồn và -i
cờ kích hoạt lời nhắc tương tác khi ghi đè lên tệp hiện có:
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
}
Mã của bạn có khoảng cách xấu trong if [ ... ]
(cần không gian trước và sau [
và trước ]
). Bạn cũng không nên thử chuyển hướng kiểm tra đến /dev/null
vì bản thân bài kiểm tra không có đầu ra. Thử nghiệm đầu tiên nên sử dụng tham số vị trí $2
, không phải chuỗi file
.
Sử dụng case ... esac
như tôi đã làm, bạn tránh phải viết thường / viết hoa phản hồi từ người dùng bằng cách sử dụng tr
. Trong bash
, nếu bạn đã muốn làm điều này bằng mọi cách, một cách rẻ hơn để làm điều đó sẽ là sử dụng REPLY="${REPLY^^}"
(cho cấp trên) hoặc REPLY="${REPLY,,}"
(cho hạ cấp).
Nếu người dùng nói "có", với mã của bạn, hàm sẽ đặt tên tệp của tệp đích vào tệp đích. Đây không phải là một bản sao của tập tin nguồn. Nó sẽ rơi vào bit sao chép thực tế của hàm.
Bit sao chép là thứ bạn đã thực hiện bằng cách sử dụng đường ống dẫn. Một đường ống được sử dụng để truyền dữ liệu từ đầu ra của một lệnh sang đầu vào của một lệnh khác. Đây không phải là điều chúng ta cần làm ở đây. Chỉ cần gọi cat
tệp nguồn và chuyển hướng đầu ra của nó sang tệp đích.
Điều tương tự là sai với bạn gọi tr
trước đó. read
sẽ đặt giá trị của một biến, nhưng không tạo ra đầu ra, do đó, đường ống read
tới bất cứ thứ gì là vô nghĩa.
Không có lối thoát rõ ràng là cần thiết trừ khi người dùng nói "không" (hoặc hàm gặp phải một số điều kiện lỗi như trong các bit của mã của tôi, nhưng vì đó là hàm tôi sử dụng return
chứ không phải exit
).
Ngoài ra, bạn đã nói "chức năng", nhưng việc thực hiện của bạn là một tập lệnh.
Hãy xem https://www.shellcheck.net/ , đây là một công cụ tốt để xác định các bit có vấn đề của các tập lệnh shell.
Sử dụng cat
chỉ là một cách để sao chép nội dung của tệp. Những cách khác bao gồm
dd if="$1" of="$2" 2>/dev/null
- Sử dụng bất kỳ tiện ích giống như bộ lọc nào có thể được tạo để chỉ truyền dữ liệu qua, ví dụ
sed "" "$1" >"2"
hoặc awk '1' "$1" >"$2"
hoặc tr '.' '.' <"$1" >"$2"
v.v.
- Vân vân.
Một mẹo nhỏ là làm cho hàm sao chép siêu dữ liệu (quyền sở hữu và quyền) từ nguồn sang đích.
Một điều cần chú ý là chức năng tôi đã viết sẽ hoạt động hoàn toàn khác so với cp
nếu mục tiêu là một cái gì đó giống như /dev/tty
ví dụ (một tệp không thông thường).