Das cp
Dienstprogramm ü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, 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. Damit mache ich printf ... >&2
.
Beachten Sie, dass wir rm
die Zieldatei nicht wirklich benötigen, da die Umleitung sie abschneidet. Wenn wir haben wollen rm
es 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 cp
tun 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 ( cat
tut 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 sh
Schalen funktionieren .
Mit etwas mehr 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 hat schlechte Abstände if [ ... ]
(benötigen vor und nach [
und vor Speicherplatz ]
). Sie sollten auch nicht versuchen, den Test umzuleiten, /dev/null
da der Test selbst keine Ausgabe hat. Der erste Test sollte außerdem den Positionsparameter verwenden $2
, nicht die Zeichenfolge file
.
Wenn Sie case ... esac
wie 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 cat
die Quelldatei auf und leiten Sie ihre Ausgabe in die Zieldatei um.
Das Gleiche ist falsch, wenn Sie tr
früher anrufen . read
Legt den Wert einer Variablen fest, erzeugt jedoch keine Ausgabe, sodass das Weiterleiten read
an 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 return
eher 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 cat
ist 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 cp
wenn das Ziel beispielsweise so etwas wie /dev/tty
eine nicht reguläre Datei ist.