![Создание собственной функции cp в bash](https://rvso.com/image/112012/%D0%A1%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%D0%BE%D0%B1%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D0%BE%D0%B9%20%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B8%20cp%20%D0%B2%20bash.png)
Для задания мне нужно было написать хитроумную функцию bash, которая имела бы ту же базовую функциональность, что и функция cp
(copy). Она должна только копировать один файл в другой, так что нет необходимости копировать несколько файлов в новый каталог.
Так как я новичок в языке 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
решение1
Утилита 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
делаетdo ( 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
например, что-то вроде (необычный файл).