Créer ma propre fonction cp dans bash

Pour une tâche, on me request d'écrire habilement une fonction bash qui a la même fonctionnalité de base que la fonction cp (copy). Il suffit de copyr un file dans un autre, donc pas de multiples files copiés dans un nouveau directory.

Depuis que je suis nouveau dans le langage bash, je ne peux pas comprendre pourquoi mon programme ne fonctionne pas. La fonction d'origine request d'écraser un file s'il est déjà présent, alors j'ai essayé de l'implémenter. Il échoue.

Le file échoue à plusieurs lignes semble-t-il, mais surtout à la condition où il vérifie si le file à copyr existe déjà ( [-e "$2"] ). Même ainsi, il affiche toujours le message qui est censé être déclenché si cette condition est remplie (le nom du file …).

Quelqu'un pourrait-il m'aider à réparer ce file, fournissant éventuellement un aperçu utile dans ma compréhension de base de la langue? Le code est comme suit.

 #!/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 "[AZ]" "[az]" if [$REPLY -eq "yes"] ; then rm "$2" echo $2 > "$2" exit 0 else exit 1 fi else cat $1 | $2 exit 0 fi 

L'utilitaire cp replacea heureusement le file cible si ce file existe déjà, sans requestr l'user.

Une fonction qui implémente la capacité cp base, sans utiliser cp serait

 cp () { cat "$1" >"$2" } 

Si vous souhaitez inviter l'user 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 au stream d'erreur standard. C'est ce que je fais avec printf ... >&2 .

Notez que nous n'avons pas vraiment besoin de rm le file cible car la redirection le tronquera. Si nous voulions d'abord le rm , il faudrait vérifier s'il s'agit d'un directory, et si c'est le cas, mettez le file cible à l'intérieur de ce directory, à la place de cp . Ceci est fait, 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 aussi vous assurer que la source existe réellement, ce qui est quelque chose que cp fait ( cat fait aussi, donc il peut être complètement ignoré, bien sûr, mais cela créerait un file 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 tous les shells.

Avec un peu plus de réglages pour prendre en charge plusieurs files source et un indicateur -i qui active l'invite interactive lors de l'écrasement d'un file 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 devriez pas non plus essayer de redirect le test vers /dev/null car le test lui-même n'a pas de sortie. Le premier test devrait en outre utiliser le paramètre de position $2 , pas le file string.

En utilisant le case ... esac comme je l'ai fait, vous évitez d'avoir à minuscules / majuscules la réponse de l'user en utilisant tr . En bash , si vous aviez voulu faire cela de toute façon, une façon less coûteuse de le faire aurait été d'utiliser REPLY="${REPLY^^}" (pour uppercasing) ou REPLY="${REPLY,,}" (pour minuscules).

Si l'user dit «oui», avec votre code, la fonction met le nom de file du file cible dans le file cible. Ceci n'est pas une copy du file source. Il devrait tomber à travers le bit de copy réel de la fonction.

Le bit de copy est quelque chose que vous avez implémenté en utilisant un pipeline. Un pipeline est utilisé pour transmettre des données de la sortie d'une command à l'input d'une autre command. Ce n'est pas quelque chose que nous devons faire ici. Il suffit d'appeler cat sur le file source et redirect sa sortie vers le file cible.

La même chose ne va pas avec votre appel de tr plus tôt. read définira la valeur d'une variable, mais ne produira pas de sortie, de sorte que la read de lignes à n'importe quoi est absurde.

Aucune sortie explicite n'est nécessaire à less que l'user ne dise «non» (ou la fonction rencontre une condition d'erreur comme dans les bits de mon code, mais puisque c'est une fonction j'utilise return plutôt que exit ).

En outre, vous avez dit "fonction", mais votre implémentation est un script.

Jetez un oeil à https://www.shellcheck.net/ , c'est un bon outil pour identifier les bits problématiques de scripts shell.


L'utilisation de cat n'est qu'une façon de copyr le contenu d'un file. D'autres moyens incluent

  • dd if="$1" of="$2" 2>/dev/null
  • En utilisant n'importe quel utilitaire de type filter qui peut être fait pour passer des données, par exemple sed "" "$1" >"2" ou awk '1' "$1" >"$2" ou tr '.' '.' <"$1" >"$2" tr '.' '.' <"$1" >"$2" tr '.' '.' <"$1" >"$2" etc.
  • etc.

Le bit difficile est de faire en sorte que la fonction copy les métadonnées (propriété et permissions) de la source vers la cible.

Une autre chose à noter est que la fonction que j'ai écrite se comportera tout à fait différemment de cp si la cible est quelque chose comme /dev/tty par exemple (un file non-régulier).