L' cp
utilità sovrascriverà felicemente il file di destinazione se quel file esiste già, senza richiedere all'utente.
Una funzione che implementa le cp
funzionalità di base , senza usare cp
sarebbe
cp () {
cat "$1" >"$2"
}
Se si desidera richiedere all'utente prima di sovrascrivere la destinazione (si noti che potrebbe non essere desiderabile farlo se la funzione viene chiamata da una shell non interattiva):
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"
}
I messaggi diagnostici dovrebbero passare al flusso di errori standard. Questo è quello che faccio printf ... >&2
.
Si noti che non è davvero necessario rm
il file di destinazione poiché il reindirizzamento lo troncherà. Se abbiamo fatto vogliamo rm
in primo luogo, allora dovreste controllare se si tratta di una directory, e se lo è, metti il file di destinazione all'interno di tale directory invece, proprio come cp
farebbe. Lo sta facendo, ma ancora senza esplicito 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"
}
Potresti anche assicurarti che la fonte esista effettivamente, il che è qualcosa cp
che fa (lo cat
fa anche lui, quindi potrebbe essere lasciato completamente fuori, ovviamente, ma farlo creerebbe un file di destinazione vuoto):
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"
}
Questa funzione non usa "bashismi" e dovrebbe funzionare in tutti gli sh
shell simili.
Con un po 'più di modifiche per supportare più file di origine e un -i
flag che attiva il prompt interattivo quando si sovrascrive un file esistente:
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
}
Il tuo codice ha spaziature errate if [ ... ]
(serve spazio prima e dopo [
e prima ]
). Inoltre, non dovresti provare a reindirizzare il test /dev/null
perché il test stesso non ha output. Il primo test dovrebbe inoltre utilizzare il parametro posizionale $2
, non la stringa file
.
Usando case ... esac
come ho fatto, eviti di dover minuscole / maiuscole la risposta dell'utente che usa tr
. In bash
, se avessi voluto farlo comunque, un modo più economico di farlo sarebbe stato quello di utilizzare REPLY="${REPLY^^}"
(per maiuscole) o REPLY="${REPLY,,}"
(per minuscole).
Se l'utente dice "sì", con il tuo codice, la funzione inserisce il nome file del file di destinazione nel file di destinazione. Questa non è una copia del file sorgente. Dovrebbe cadere nel bit di copia effettivo della funzione.
Il bit di copia è qualcosa che hai implementato usando una pipeline. Una pipeline viene utilizzata per passare i dati dall'output di un comando all'input di un altro comando. Questo non è qualcosa che dobbiamo fare qui. Richiama semplicemente cat
il file di origine e reindirizza il suo output sul file di destinazione.
La stessa cosa è sbagliata quando chiami tr
prima. read
imposterà il valore di una variabile, ma non produce alcun output, quindi il piping read
su qualsiasi cosa è privo di senso.
Non è necessaria alcuna uscita esplicita a meno che l'utente non dica "no" (o la funzione rileva una condizione di errore come nei bit del mio codice, ma poiché è una funzione che uso return
piuttosto che exit
).
Inoltre, hai detto "funzione", ma l'implementazione è uno script.
Dai un'occhiata a https://www.shellcheck.net/ , è un ottimo strumento per identificare bit problematici di script di shell.
L'utilizzo cat
è solo un modo per copiare il contenuto di un file. Altri modi includono
dd if="$1" of="$2" 2>/dev/null
- Utilizzo di qualsiasi utilità simile a un filtro che può essere creata per passare semplicemente i dati, ad esempio
sed "" "$1" >"2"
o awk '1' "$1" >"$2"
o tr '.' '.' <"$1" >"$2"
ecc.
- eccetera.
Il trucco è che la funzione copia i metadati (proprietà e autorizzazioni) dall'origine alla destinazione.
Un'altra cosa da notare è che la funzione che ho scritto si comporterà in modo abbastanza diverso cp
se la destinazione è qualcosa di simile, /dev/tty
ad esempio (un file non regolare).