Erstellen meiner eigenen CP-Funktion in Bash

Erstellen meiner eigenen CP-Funktion in Bash

Für eine Aufgabe soll ich eine Bash-Funktion konstruieren, die die gleiche Grundfunktionalität hat wie die Funktion cp(Kopieren). Sie muss nur eine Datei in eine andere kopieren, also nicht mehrere Dateien in ein neues Verzeichnis kopieren.

Da ich neu in der Bash-Sprache bin, kann ich nicht verstehen, warum mein Programm nicht funktioniert. Die ursprüngliche Funktion fordert zum Überschreiben einer bereits vorhandenen Datei auf, also habe ich versucht, dies zu implementieren. Es schlägt fehl.

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

Könnte mir jemand dabei helfen, diese Datei zu reparieren und mir möglicherweise dabei helfen, meine grundlegenden Sprachkenntnisse zu verbessern? 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

Antwort1

Das cpDienstprogramm überschreibt die Zieldatei, wenn diese bereits vorhanden ist, ohne Nachfrage beim Benutzer.

Eine Funktion, die grundlegende cpFähigkeiten implementiert, ohne 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. Das mache ich mit printf ... >&2.

Beachten Sie, dass wir die Zieldatei nicht wirklich brauchen, rmda die Umleitung sie abschneidet. Wenn wirtatWenn Sie es zuerst möchten rm, müssen Sie prüfen, ob es sich um ein Verzeichnis handelt, und wenn ja, die Zieldatei stattdessen in dieses Verzeichnis legen, so wie es auch cpfunktionieren würde. Dies tut dies, aber immer noch ohne explizites 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"
}

Sie möchten vielleicht auch sicherstellen, dass die Quelle tatsächlich existiert, was etwas istcp tutdo ( cattut es auch, also kann es natürlich auch ganz weggelassen werden, aber dadurch würde eine leere Zieldatei erstellt):

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 allen sh-ähnlichen Shells funktionieren.

Mit ein paar weiteren 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 weist falsche Abstände in auf if [ ... ](es wird Platz vor und nach [und vor benötigt ]). Sie sollten auch nicht versuchen, den Test auf umzuleiten, /dev/nullda der Test selbst keine Ausgabe hat. Der erste Test sollte außerdem den Positionsparameter verwenden $2, nicht den String file.

Wenn Sie wie ich verwenden case ... esac, müssen Sie die Antwort des Benutzers nicht in Klein- oder Großbuchstaben schreiben tr. bashWenn Sie dies in ohnehin tun wollten, wäre die Verwendung von REPLY="${REPLY^^}"(für Großbuchstaben) oder REPLY="${REPLY,,}"(für Kleinbuchstaben) eine kostengünstigere Methode gewesen.

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 Kopieren 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 deren Ausgabe an die Zieldatei um.

Das Gleiche ist bei Ihrem trfrüheren Aufruf falsch. readlegt den Wert einer Variablen fest, erzeugt aber keine Ausgabe, sodass eine Weiterleitung readan irgendetwas unsinnig ist.

Es ist kein explizites Beenden erforderlich, es sei denn, der Benutzer sagt „Nein“ (oder die Funktion stößt auf einen Fehlerzustand, wie in Teilen meines Codes, aber da es sich um eine Funktion handelt, verwende ich returnanstelle von exit).

Außerdem haben Sie „Funktion“ gesagt, aber Ihre Implementierung ist ein Skript.

Schauen Sie sich doch einmal anhttps://www.shellcheck.net/, es ist ein gutes Tool zum Identifizieren problematischer Teile von Shell-Skripten.


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

  • dd if="$1" of="$2" 2>/dev/null
  • Verwenden Sie ein filterähnliches Dienstprogramm, das so eingestellt werden kann, dass Daten einfach durchgelassen werden, z. B. sed "" "$1" >"2"oder awk '1' "$1" >"$2"oder tr '.' '.' <"$1" >"$2"usw.
  • usw.

Der schwierige Teil besteht darin, die Funktion dazu zu bringen, die Metadaten (Eigentum und Berechtigungen) von der Quelle zum Ziel zu kopieren.

Außerdem ist zu beachten, dass sich die von mir geschriebene Funktion ganz anders verhält, als cpwenn das Ziel beispielsweise /dev/ttyeine nicht reguläre Datei wäre.

verwandte Informationen