L' cp
utilitaire remplacera volontiers le fichier cible si ce fichier existe déjà, sans inviter l'utilisateur.
Une fonction qui implémente une cp
capacité de base , sans utiliser cp
serait
cp () {
cat "$1" >"$2"
}
Si vous souhaitez inviter l'utilisateur avant d'écraser la cible (notez qu'il peut ne pas être souhaitable de le faire si la fonction est appelée par un shell non interactif):
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"
}
Les messages de diagnostic doivent aller dans le flux d'erreur standard. C'est ce que je fais avec printf ... >&2
.
Notez que nous n'avons pas vraiment besoin rm
du fichier cible car la redirection le tronquera. Si nous ne voulons rm
d'abord, alors vous devriez vérifier si elle est un répertoire, et si elle est, placez le fichier cible à l' intérieur dans le répertoire correspondant , juste la façon dont le cp
ferais. Cela fait cela, mais toujours sans explicite 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"
}
Vous pouvez également vous assurer que la source existe réellement, ce qui est le cp
cas (le cat
fait aussi, donc il peut être complètement ignoré, mais cela créerait un fichier cible vide):
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"
}
Cette fonction n'utilise pas de "bashismes" et devrait fonctionner dans des sh
coques similaires.
Avec un peu plus de réglages pour prendre en charge plusieurs fichiers source et un -i
indicateur qui active l'invite interactive lors du remplacement d'un fichier existant:
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
}
Votre code a de mauvais espacements if [ ... ]
(besoin d'espace avant et après [
et avant ]
). Vous ne devez pas non plus essayer de rediriger le test vers /dev/null
car le test lui-même n'a pas de sortie. Le premier test doit en outre utiliser le paramètre positionnel $2
, pas la chaîne file
.
En utilisant case ... esac
comme je l'ai fait, vous évitez d'avoir à minuscules / majuscules la réponse de l'utilisateur qui utilise tr
. Dans bash
, si vous aviez voulu le faire de toute façon, une manière moins coûteuse de le faire aurait été d'utiliser REPLY="${REPLY^^}"
(pour les majuscules) ou REPLY="${REPLY,,}"
(pour les minuscules).
Si l'utilisateur dit "oui", avec votre code, la fonction met le nom de fichier du fichier cible dans le fichier cible. Ce n'est pas une copie du fichier source. Il doit passer par le bit de copie réel de la fonction.
Le bit de copie est quelque chose que vous avez implémenté à l'aide d'un pipeline. Un pipeline est utilisé pour transmettre des données de la sortie d'une commande à l'entrée d'une autre commande. Ce n'est pas quelque chose que nous devons faire ici. Appelez simplement cat
le fichier source et redirigez sa sortie vers le fichier cible.
La même chose ne va pas lorsque vous appelez tr
plus tôt. read
définira la valeur d'une variable, mais ne produit aucune sortie, donc le raccordement read
à n'importe quoi est absurde.
Aucune sortie explicite n'est nécessaire à moins que l'utilisateur ne dise "non" (ou que la fonction rencontre une condition d'erreur comme dans les bits de mon code, mais puisque c'est une fonction que j'utilise return
plutôt que exit
).
Vous avez également dit "fonction", mais votre implémentation est un script.
Jetez un œil à https://www.shellcheck.net/ , c'est un bon outil pour identifier les bits problématiques des scripts shell.
L'utilisation cat
n'est qu'une façon de copier le contenu d'un fichier. D'autres moyens incluent
dd if="$1" of="$2" 2>/dev/null
- Utiliser n'importe quel utilitaire de type filtre qui peut être fait pour simplement transmettre des données, par exemple
sed "" "$1" >"2"
ou awk '1' "$1" >"$2"
ou tr '.' '.' <"$1" >"$2"
etc.
- etc.
L'astuce consiste à faire en sorte que la fonction copie les métadonnées (propriété et autorisations) de la source vers la cible.
Une autre chose à noter est que la fonction que j'ai écrite se comportera très différemment cp
si la cible est quelque chose comme /dev/tty
par exemple (un fichier non régulier).