Утилита cp
перезапишет целевой файл, если этот файл уже существует, без запроса пользователя.
Функция, которая реализует базовую возможность cp
,без использования cp
было бы
cp () {
cat "$1" >"$2"
}
Если вы хотите запросить пользователя перед перезаписью цели (, обратите внимание, что это может быть нежелательно, если функция вызывается не -интерактивной оболочкой):
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"
}
Эта функция не использует "башизмы" и должна работать во всехsh
-подобных оболочках.
С небольшой дополнительной настройкой для поддержки нескольких исходных файлов и флагом -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^^}"
(для верхнего регистра )илиREPLY="${REPLY,,}"
(для нижнего регистра ).
Если пользователь говорит "да", с вашим кодом функция помещает имя файла целевого файла в целевой файл.Это не копирование исходного файла. Он должен провалиться до фактического бита копирования функции.
Бит копирования — это то, что вы реализовали с помощью конвейера. Конвейер используется для передачи данных с вывода одной команды на ввод другой команды. Это не то, что нам нужно делать здесь. Просто вызовите cat
в исходном файле и перенаправьте его вывод в целевой файл.
То же самое не так с вашим звонком tr
ранее. read
устанавливает значение переменной, но не производит никакого вывода, поэтому передача read
чему-либо бессмысленна.
Явный выход не требуется, если только пользователь не скажет «нет» (или функция не обнаружит какое-либо состояние ошибки, как в битах моего кода, но поскольку это функция, я использую return
, а неexit
).
Кроме того, вы сказали «функция», но ваша реализация — это скрипт.
Взгляните на https://www.shellcheck.net/, это хороший инструмент для выявления проблемных фрагментов сценариев оболочки.
Использование cat
— это всего лишь один способ копирования содержимого файла. Другие способы включают
dd if="$1" of="$2" 2>/dev/null
sed "" "$1" >"2"
или awk '1' "$1" >"$2"
или tr '.' '.' <"$1" >"$2"
и т. д. Хитрость заключается в том, чтобы заставить функцию копировать метаданные (права собственности и разрешения )из источника в цель.
Следует также отметить, что функция, которую я написал, будет вести себя совершенно иначе, чем cp
, если целью является что-то вроде /dev/tty
, например (не -обычный файл ).