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

8

Для задания меня просят умно написать функцию bash, которая имеет те же основные функции, что и функция cp(копия). Нужно только скопировать один файл в другой, поэтому несколько файлов не будут скопированы в новый каталог.

Поскольку я новичок в языке 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
timmaay92
источник
17
Начните с www.shellcheck.net
SteelDriver
2
Если бы это было рецензия код: не когда - либо делать [ ... ] &> /dev/null. Поддерживаемые тесты [ ... ]всегда молчат, только в случае синтаксических ошибок выдается любой вывод. Глушить такой тест - это просто копать собственную могилу.
Муру
1
Одно замечание: cat $1 | $2« направляет » вывод первой команды к команде из второй переменной. Но это имя файла, а не имя команды для выполнения.
Флорис
Вам нужно пересмотреть основной синтаксис [команды в вашем if.
Бармар,
И вам нужно узнать разницу между передачей |в файл и перенаправлением в файл с помощью >. Это основные проблемы синтаксиса, которые уже должны были быть рассмотрены в вашем классе.
Бармар

Ответы:

26

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например (нестандартный файл).

Кусалананда
источник
чтобы добавить некоторую интересную точность о том, как это работает:: cat "$1" >"$2" будет работать в 2 раза: оболочка сначала увидит > "$2", что означает: create-or-clobber (= empty) файл "$ 2" и перенаправить стандартный вывод команды в этот файл. Затем он запускает команду: cat "$1"и перенаправляет свой стандартный вывод в файл "$2"(уже пустой или созданный пустой).
Оливье Дюлак
Один из лучших ответов, которые я видел здесь! Я узнал о -efсравнении файлов и local -a.
садовник
@gardenhead Да, -efпринимает уведомления, если два файла совпадают (также заботится о ссылках) и local -aсоздает переменную локального массива.
Кусалананда
Спасибо огромное! Действительно хороший, продуманный ответ, очень полезный для понимания языка
timmaay92