Creando mi propia función cp en bash

Creando mi propia función cp en bash

Para una tarea, me piden que escriba inteligentemente una función bash que tenga la misma funcionalidad básica que la función cp(copiar). Solo tiene que copiar un archivo a otro, por lo que no se copian varios archivos en un nuevo directorio.

Como soy nuevo en el lenguaje bash, no puedo entender por qué mi programa no funciona. La función original solicita sobrescribir un archivo si ya está presente, así que intenté implementarlo. Falla.

Parece que el archivo falla en varias líneas, pero lo más importante es en la condición en la que verifica si el archivo que se va a copiar ya existe ( [-e "$2"]). Aun así, todavía muestra el mensaje que se supone que se activará si se cumple esa condición (El nombre del archivo...).

¿Alguien podría ayudarme a arreglar este archivo, posiblemente brindándome información útil sobre mi comprensión básica del idioma? El código es el siguiente.

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

Respuesta1

La cputilidad sobrescribirá felizmente el archivo de destino si ese archivo ya existe, sin avisar al usuario.

Una función que implementa cpla capacidad básica, sin usar cpsería

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

Si desea avisar al usuario antes de sobrescribir el destino (tenga en cuenta que puede que no sea conveniente hacer esto si la función es llamada por un shell no interactivo):

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

Los mensajes de diagnóstico deben ir al flujo de error estándar. Esto es lo que hago con printf ... >&2.

Tenga en cuenta que realmente no necesitamos rmel archivo de destino ya que la redirección lo truncará. Si nosotroshizoSi lo desea rmprimero, entonces tendrá que verificar si es un directorio y, si lo es, colocar el archivo de destino dentro de ese directorio, tal como cpsería suficiente. Esto está haciendo eso, pero aún sin ser 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"
}

También es posible que desees asegurarte de que la fuente realmente exista, lo cual es algocp hacedo ( cattambién lo hace, por lo que puede omitirse por completo, por supuesto, pero hacerlo crearía un archivo de destino vacío):

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 función no utiliza "bashismos" y debería funcionar en todos shlos shells.

Con un poco más de ajustes para admitir múltiples archivos fuente y un -iindicador que activa el mensaje interactivo al sobrescribir un archivo 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
}

Su código tiene espacios incorrectos if [ ... ](necesita espacio antes y después [y antes ]). Tampoco debería intentar redirigir la prueba /dev/nullya que la prueba en sí no tiene resultados. Además, la primera prueba debería utilizar el parámetro posicional $2, no la cadena file.

Usando case ... esaccomo lo hice yo, evitas tener que escribir en minúsculas/mayúsculas la respuesta del usuario que usa tr. En bash, si hubiera querido hacer esto de todos modos, una forma más económica de hacerlo habría sido usar REPLY="${REPLY^^}"(para mayúsculas) o REPLY="${REPLY,,}"(para minúsculas).

Si el usuario dice "sí", con su código, la función coloca el nombre del archivo de destino en el archivo de destino. Esta no es una copia del archivo fuente. Debería pasar al bit de copia real de la función.

El bit de copia es algo que ha implementado mediante una canalización. Una canalización se utiliza para pasar datos desde la salida de un comando a la entrada de otro comando. Esto no es algo que debamos hacer aquí. Simplemente invoque catel archivo fuente y redirija su salida al archivo de destino.

Lo mismo ocurre cuando llamaste trantes. readestablecerá el valor de una variable, pero no produce ningún resultado, por lo que conectar readcualquier cosa no tiene sentido.

No se necesita una salida explícita a menos que el usuario diga "no" (o la función encuentre alguna condición de error como en bits de mi código, pero ya que es una función que uso returnen lugar de exit).

Además, dijiste "función", pero tu implementación es un script.

Echa un vistazo ahttps://www.shellcheck.net/, es una buena herramienta para identificar partes problemáticas de scripts de shell.


Usar cates solounoforma de copiar el contenido de un archivo. Otras formas incluyen

  • dd if="$1" of="$2" 2>/dev/null
  • Usar cualquier utilidad similar a un filtro que se pueda hacer para simplemente pasar datos, por ejemplo, sed "" "$1" >"2"o awk '1' "$1" >"$2", tr '.' '.' <"$1" >"$2"etc.
  • etc.

El truco es hacer que la función copie los metadatos (propiedad y permisos) del origen al destino.

Otra cosa a tener en cuenta es que la función que he escrito se comportará de manera bastante diferente a cpsi el objetivo fuera algo como, /dev/ttypor ejemplo, (un archivo no normal).

información relacionada