Criando minha própria função cp no bash

Criando minha própria função cp no bash

Para uma tarefa, sou solicitado a escrever de maneira inteligente uma função bash que tenha a mesma funcionalidade básica da função cp(cópia). Ele só precisa copiar um arquivo para outro, portanto, não há vários arquivos copiados para um novo diretório.

Como sou novo na linguagem bash, não consigo entender por que meu programa não está funcionando. A função original pede para substituir um arquivo se ele já estiver presente, então tentei implementar isso. Falha.

Parece que o arquivo falha em várias linhas, mas o mais importante é na condição em que verifica se o arquivo a ser copiado já existe ( [-e "$2"]). Mesmo assim, ainda mostra a mensagem que deve ser acionada se essa condição for atendida (O nome do arquivo...).

Alguém poderia me ajudar a consertar este arquivo, possivelmente fornecendo algumas informações úteis na minha compreensão básica da linguagem? O código é o seguinte.

#!/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

Responder1

O cputilitário substituirá com prazer o arquivo de destino se esse arquivo já existir, sem avisar o usuário.

Uma função que implementa cpcapacidade básica, sem usar cpseria

cp () {
    cat "$1" >"$2"
}

Se você quiser avisar o usuário antes de substituir o destino (observe que pode não ser desejável fazer isso se a função for chamada por um shell não interativo):

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"
}

As mensagens de diagnóstico devem ir para o fluxo de erros padrão. Isso é o que eu faço com printf ... >&2.

Observe que realmente não precisamos do rmarquivo de destino, pois o redirecionamento irá truncá-lo. Se nósfezquiser rmprimeiro, então você terá que verificar se é um diretório e, se for, coloque o arquivo de destino dentro desse diretório, exatamente como cpfaria. Isso está fazendo isso, mas ainda sem explícito 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"
}

Você também pode querer ter certeza de que a fonte realmente existe, o que é algocp fazdo ( catfaz isso também, então pode ser deixado completamente de fora, é claro, mas isso criaria um arquivo de destino vazio):

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"
}

Esta função não usa "bashismos" e deve funcionar em todos shos shells semelhantes.

Com um pouco mais de ajustes para suportar vários arquivos de origem e um -isinalizador que ativa o prompt interativo ao substituir um arquivo existente:

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
}

Seu código tem espaçamentos ruins if [ ... ](precisa de espaço antes e depois [e antes ]). Você também não deve tentar redirecionar o teste, /dev/nullpois o teste em si não tem saída. Além disso, o primeiro teste deve usar o parâmetro posicional $2, não a string file.

Usando case ... esaccomo eu fiz, você evita ter que colocar letras minúsculas/maiúsculas na resposta do usuário usando tr. Em bash, se você quisesse fazer isso de qualquer maneira, uma maneira mais barata de fazer isso seria usar REPLY="${REPLY^^}"(para letras maiúsculas) ou REPLY="${REPLY,,}"(para letras minúsculas).

Se o usuário disser "sim", com o seu código, a função colocará o nome do arquivo de destino no arquivo de destino. Esta não é uma cópia do arquivo de origem. Deve cair no bit de cópia real da função.

A parte de cópia é algo que você implementou usando um pipeline. Um pipeline é usado para passar dados da saída de um comando para a entrada de outro comando. Isso não é algo que precisamos fazer aqui. Simplesmente invoque cato arquivo de origem e redirecione sua saída para o arquivo de destino.

A mesma coisa está errada com você ligando trmais cedo. readdefinirá o valor de uma variável, mas não produzirá saída, portanto, canalizar readpara qualquer coisa não faz sentido.

Nenhuma saída explícita é necessária, a menos que o usuário diga "não" (ou a função encontre alguma condição de erro como em bits do meu código, mas como é uma função que uso returnem vez de exit).

Além disso, você disse "função", mas sua implementação é um script.

Dê uma olhadahttps://www.shellcheck.net/, é uma boa ferramenta para identificar partes problemáticas de scripts de shell.


Usar caté apenasummaneira de copiar o conteúdo de um arquivo. Outras maneiras incluem

  • dd if="$1" of="$2" 2>/dev/null
  • Usando qualquer utilitário semelhante a um filtro que possa ser feito apenas para passar dados, por exemplo, sed "" "$1" >"2"ou awk '1' "$1" >"$2"ou tr '.' '.' <"$1" >"$2"etc.
  • etc.

O complicado é fazer com que a função copie os metadados (propriedade e permissões) da origem para o destino.

Outra coisa a notar é que a função que escrevi se comportará de maneira bem diferente de cpse o destino for algo como, /dev/ttypor exemplo, (um arquivo não regular).

informação relacionada