Создание собственной функции cp в bash

Создание собственной функции cp в bash

Для задания мне нужно было написать хитроумную функцию 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например, что-то вроде (необычный файл).

Связанный контент