Erstellen meiner eigenen CP-Funktion in Bash

8

Für eine Aufgabe werde ich gebeten, geschickt eine Bash-Funktion zu schreiben, die die gleiche Grundfunktionalität wie die Funktion cp(Kopie) hat. Es muss nur eine Datei in eine andere kopiert werden, sodass nicht mehrere Dateien in ein neues Verzeichnis kopiert werden.

Da ich neu in der Bash-Sprache bin, kann ich nicht verstehen, warum mein Programm nicht funktioniert. Die ursprüngliche Funktion fordert Sie auf, eine Datei zu überschreiben, wenn sie bereits vorhanden ist. Deshalb habe ich versucht, dies zu implementieren. Es schlägt fehl.

Die Datei schlägt anscheinend in mehreren Zeilen fehl, vor allem aber unter der Bedingung, dass überprüft wird, ob die zu kopierende Datei bereits vorhanden ist ( [-e "$2"]). Trotzdem wird die Meldung angezeigt, die ausgelöst werden soll, wenn diese Bedingung erfüllt ist (Der Dateiname ...).

Könnte mir jemand beim Reparieren dieser Datei helfen und möglicherweise nützliche Einblicke in mein grundlegendes Verständnis der Sprache geben? Der Code lautet wie folgt.

#!/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
quelle
17
Beginnen Sie mit www.shellcheck.net
steeldriver
2
Wenn dieser Code - Review waren: nicht immer tun [ ... ] &> /dev/null. Die von unterstützten Tests [ ... ]sind immer stumm. Nur bei Syntaxfehlern wird eine Ausgabe erzeugt. Wenn Sie einen solchen Test zum Schweigen bringen, graben Sie einfach Ihr eigenes Grab.
Muru
1
Ein Punkt: cat $1 | $2"leitet" die Ausgabe des ersten Befehls an den Befehl in Ihrer zweiten Variablen weiter. Dies ist jedoch ein Dateiname, nicht der Name eines auszuführenden Befehls.
Floris
Sie müssen die grundlegende Syntax des [Befehls in Ihrem überprüfen if.
Barmar
Und Sie müssen den Unterschied zwischen Piping mit |und Umleiten in eine Datei mit lernen >. Dies sind grundlegende Syntaxprobleme, die in Ihrer Klasse bereits behandelt werden sollten.
Barmar

Antworten:

26

Das cpDienstprogramm überschreibt die Zieldatei gerne, wenn diese Datei bereits vorhanden ist, ohne den Benutzer dazu aufzufordern.

Eine Funktion, die grundlegende Funktionen implementiert cp, ohne sie zu verwenden, cpwäre

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

Wenn Sie den Benutzer vor dem Überschreiben des Ziels auffordern möchten (beachten Sie, dass dies möglicherweise nicht wünschenswert ist, wenn die Funktion von einer nicht interaktiven Shell aufgerufen wird):

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

Die Diagnosemeldungen sollten an den Standardfehlerstrom gesendet werden. Damit mache ich printf ... >&2.

Beachten Sie, dass wir rmdie Zieldatei nicht wirklich benötigen, da die Umleitung sie abschneidet. Wenn wir haben wollen rmes zuerst, dann würden Sie haben zu prüfen , ob es sich um ein Verzeichnis ist, und wenn es ist, legen Sie die Zieldatei in diesem Verzeichnis statt, sondern nur die Art und Weise cptun würde. Dies tut das, aber immer noch ohne explizite 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"
}

Möglicherweise möchten Sie auch sicherstellen, dass die Quelle tatsächlich vorhanden ist, was etwas cp bewirkt ( cattut es auch, sodass es natürlich vollständig weggelassen werden kann, aber dies würde eine leere Zieldatei erstellen):

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

Diese Funktion verwendet keine "Bashismen" und sollte in allähnlichen shSchalen funktionieren .

Mit etwas mehr Optimierungen zur Unterstützung mehrerer Quelldateien und einem -iFlag, das die interaktive Eingabeaufforderung beim Überschreiben einer vorhandenen Datei aktiviert:

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
}

Ihr Code hat schlechte Abstände if [ ... ](benötigen vor und nach [und vor Speicherplatz ]). Sie sollten auch nicht versuchen, den Test umzuleiten, /dev/nullda der Test selbst keine Ausgabe hat. Der erste Test sollte außerdem den Positionsparameter verwenden $2, nicht die Zeichenfolge file.

Wenn Sie case ... esacwie ich verwenden, müssen Sie die Antwort des Benutzers mit Klein- / Großbuchstaben vermeiden tr. In bash, wenn Sie diese ohnehin tun wollte , war, eine billigere Art und Weise , es zu tun zu verwenden gewesen wäre REPLY="${REPLY^^}"(für großgeschrieben) oder REPLY="${REPLY,,}"(für Kleinschreibung).

Wenn der Benutzer mit Ihrem Code "Ja" sagt, fügt die Funktion den Dateinamen der Zieldatei in die Zieldatei ein. Dies ist kein Kopieren der Quelldatei. Es sollte bis zum eigentlichen Kopierbit der Funktion durchfallen.

Das Kopierbit haben Sie mithilfe einer Pipeline implementiert. Eine Pipeline wird verwendet, um Daten von der Ausgabe eines Befehls an die Eingabe eines anderen Befehls zu übergeben. Das müssen wir hier nicht tun. Rufen Sie einfach catdie Quelldatei auf und leiten Sie ihre Ausgabe in die Zieldatei um.

Das Gleiche ist falsch, wenn Sie trfrüher anrufen . readLegt den Wert einer Variablen fest, erzeugt jedoch keine Ausgabe, sodass das Weiterleiten readan irgendetwas unsinnig ist.

Es ist kein explizites Beenden erforderlich, es sei denn, der Benutzer sagt "Nein" (oder die Funktion stößt auf eine Fehlerbedingung wie in Teilen meines Codes, aber da es sich um eine Funktion handelt, die ich returneher verwende als exit).

Sie sagten auch "Funktion", aber Ihre Implementierung ist ein Skript.

Schauen Sie sich https://www.shellcheck.net/ an , es ist ein gutes Werkzeug, um problematische Teile von Shell-Skripten zu identifizieren.


Die Verwendung catist nur eine Möglichkeit, den Inhalt einer Datei zu kopieren. Andere Möglichkeiten sind

  • dd if="$1" of="$2" 2>/dev/null
  • Unter Verwendung eines beliebigen Filter Programm wie die gemacht werden können , um nur Daten durchlaufen, zB sed "" "$1" >"2"oder awk '1' "$1" >"$2"oder tr '.' '.' <"$1" >"$2"usw.
  • usw.

Das Knifflige ist, dass die Funktion die Metadaten (Besitz und Berechtigungen) von der Quelle zum Ziel kopiert.

Eine andere Sache, die zu beachten ist, ist, dass sich die von mir geschriebene Funktion ganz anders verhält, als cpwenn das Ziel beispielsweise so etwas wie /dev/ttyeine nicht reguläre Datei ist.

Kusalananda
quelle
Um eine interessante Präzision in Bezug auf die Funktionsweise hinzuzufügen :: cat "$1" >"$2" wird in zwei > "$2"Fällen funktionieren: Die Shell sieht zuerst , was bedeutet: Erstellen oder Clobber (= leer) der Datei "$ 2", und leiten Sie die Standardausgabe des Befehls in diese Datei um. Dann startet es den Befehl: cat "$1"und leitet seine Standardausgabe in eine Datei um "$2"(die bereits geleert oder leer erstellt wurde).
Olivier Dulac
Eine der besten Antworten, die ich hier gesehen habe! Ich habe sowohl über -efDateivergleiche als auch über gelernt local -a.
Gardenhead
@gardenhead Ja, -efnimmt zur Kenntnis, ob die beiden Dateien identisch sind (kümmert sich auch um Links) und local -aerstellt eine lokale Array-Variable.
Kusalananda
Ich danke dir sehr! Wirklich nette, ausführliche Antwort, sehr hilfreich beim Verstehen der Sprache
timmaay92