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 cp
Dienstprogramm überschreibt die Zieldatei, wenn diese bereits vorhanden ist, ohne Nachfrage beim Benutzer.
Eine Funktion, die grundlegende cp
Fähigkeiten implementiert, ohne zu verwenden, cp
wä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, rm
da 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 cp
funktionieren 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 ( cat
tut 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 -i
Flag, 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/null
da 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
. bash
Wenn 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 cat
die Quelldatei auf und leiten Sie deren Ausgabe an die Zieldatei um.
Das Gleiche ist bei Ihrem tr
früheren Aufruf falsch. read
legt den Wert einer Variablen fest, erzeugt aber keine Ausgabe, sodass eine Weiterleitung read
an 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 return
anstelle 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 cat
ist 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"
oderawk '1' "$1" >"$2"
odertr '.' '.' <"$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 cp
wenn das Ziel beispielsweise /dev/tty
eine nicht reguläre Datei wäre.