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"
}
Эта функция не использует "bashisms" и должна работать во всех 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,,}"
(для lowercasing).
Если пользователь говорит «да» с вашим кодом, функция помещает имя файла целевого файла в целевой файл. Это не копирование исходного файла. Это должно провалиться до фактического бита копирования функции.
Бит копирования - это то, что вы реализовали с помощью конвейера. Конвейер используется для передачи данных с выхода одной команды на вход другой команды. Это не то, что нам нужно делать здесь. Просто вызовите 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
например (нестандартный файл).