web-dev-qa-db-fra.com

Script pour échanger les noms de deux fichiers

Je suis nouveau dans l'écriture de scripts Bash. J'ai essayé de faire un script qui échangera les noms de fichiers des deux fichiers qui lui ont été transmis par l'utilisateur.

Voici ne image des deux versions de mon script jusqu'à présent

Voici le script au format texte avec mv:

#! /bin/bash
file1=$file1
file2=$file2

echo "write file name :"
read file1 file2

if [[ $file1 = $file2 ]]
then 
  cp $file1 $file1
  mv $file1 $file2 
fi

if [[ $file2 = $file1 ]]
then   
  mv $file2 $file1
fi

Mais ma question est si je peux faire un script qui laisse l’utilisateur écrire 2 noms de fichiers d’abord, puis le script échangera les 2 noms de fichiers

La base de l'échange de noms de fichiers ce que j'ai lu est ceci

cp $file1 temporaryfile
mv $file1 $file2
mv $file2 temporyfile
7
DeadPool69

Une façon possible de le faire

Il y a quelque temps, j'ai créé une fonction spécialement à cet effet que je garde dans mon .bashrc et que je peux adapter à un script. Vous devriez tirer parti de paramètres de position pour que les utilisateurs puissent mettre les noms de fichiers en ligne de commande. Voici ma fonction d'origine:

swap_files() {
    if [ $# -ne 2 ]
    then
        echo "Usage: swap_files file1 file2"
    else
        local TMPFILE=$(mktemp) 
        mv -- "$1" "$TMPFILE"
        mv -- "$2" "$1"
        mv -- "$TMPFILE" "$2"
    fi
}

Vous pouvez vous débarrasser de la déclaration swap_files(){, du mot clé local et de la fermeture de }, puis le transformer en script. Ajoutez simplement #!/bin/bash en haut. Certes, il y a des tas de choses qui peuvent être améliorées, mais au niveau de base, c'est aussi simple que d'échanger (ce qui est d'ailleurs enseigné fréquemment en C pour échanger des éléments de tableau, mais ce n'est qu'un sujet tangent).

#!/bin/bash
if [ $# -ne 2 ]
then
    printf "Usage: swap_files file1 file2\n" > /dev/stderr
else
    TMPFILE=$(mktemp)
    mv -- "$1" "$TMPFILE"
    mv -- "$2" "$1"
    mv -- "$TMPFILE" "$2"
fi

Bien sûr, n'oubliez pas de citer les paramètres de position si les noms de fichiers contiennent des espaces. Ainsi:

swap_files 'file 1' 'file 2'

Notez l'utilisation de -- pour éviter les problèmes de noms de fichiers contenant - en tête. Un meilleur moyen serait de prendre l'habitude de référencer des fichiers dans le répertoire de travail actuel avec ./, surtout si vous utilisez globstar * (globstar n'est pas pertinent dans cette question, mais il vaut la peine de le mentionner si nous ' Parlez des noms de fichiers avec -). De plus, ./ way est plus portable, car certaines versions de mv telles que sous FreeBSD ne disposent pas de l'option --.


Comme suggéré par terdon dans les commentaires, nous pouvons également créer un fichier temporaire dans le dossier parent du premier fichier pour éviter le déplacement de fichiers entre systèmes de fichiers.

#!/bin/bash
if [ $# -ne 2 ]
then
    printf "Usage: swap_files file1 file2\n" > /dev/stderr
else
    file1_dir=${1%/*}
    # just in case there were no slashes removed, assume cwd
    if [ "$file1_dir" = "$1" ]; then
        file1_dir="."
    fi
    tmpfile=$(mktemp -p "$file1_dir" )
    mv -- "$1" "$tmpfile"
    mv -- "$2" "$1"
    mv -- "$tmpfile" "$2"
fi

Votre script et les choses à améliorer

1. Affectation de variable redondante

file1=$file1
file2=$file2

Cette partie attribue la variable $file1 à ... file1 variable; Il y a deux problèmes avec ceci: l'affectation d'une variable à elle-même est redondante, et elle n'existe pas pour commencer, il n'y a pas de déclaration de cette variable plus tôt dans le script.

2. Méfiez-vous des division de mots en lecture

Voici ce qui se passera si votre utilisateur tente de mettre des éléments même entre guillemets dans votre commande read:

$ read file1 file2
'one potato' 'two potato'

$ echo "$file1"
'one

$ echo "$file2"
potato' 'two potato'

Conformément au comportement du shell, le shell scinde tout ce qui est lu sur stdin et tente d'adapter chaque mot aux variables correspondantes. Si les mots dépassent le nombre de variables, il essaie de tout insérer dans la dernière variable. Je vous recommande de lire chaque fichier, un à la fois.

3. Copier sur soi est une erreur

Tu fais

cp $file1 $file1;

Ça va produire une erreur

$ cp input.txt input.txt
cp: 'input.txt' and 'input.txt' are the same file

Peut-être que tu voulais faire

cp "$file1" "$file1".tmp

Ou utilisez simplement la commande mktemp comme je le faisais. Notez également la citation de variables pour éviter le fractionnement de Word.


Autres façons amusantes de le faire

Saviez-vous que vous pouvez classer n'importe quel fichier avec redirection pour effectuer une copie? Donc, utiliser mv ou cp n'est pas le seul moyen. Quelque chose comme ça:

$ cat ./wallpaper.jpg > wallpaper.jpg.tmp
$ cat ./screenshot.jpg > wallpaper.jpg
$ cat ./wallpaper.jpg.tmp > ./screenshot.jpg
11

Vous pouvez utiliser le développement de paramètres pour la tâche si vous obtenez vos deux noms de fichiers ou vous pouvez les lire dans le script. L'exemple de script ci-dessous utilise le développement de paramètres. Et vous voudrez peut-être utiliser un répertoire temporaire pour vos options de déplacement, car si le nom de fichier utilisé dans le script existe déjà, ce fichier sera automatiquement remplacé.

#!/bin/bash

# creating a temporary directory to prevent overwrites if the file called 'tmpfile' exists
TMP=$(mktemp -d)

# copying the filenames into variables for later use
file1="$1"
file2="$2"

# swapping the files
# first move and rename file1 to $TMP/tempfile
mv "$1" $TMP/tempfile
# renaming file2 to file1 name
mv "$2" "$file1"
# now renaming and moving the tempfile to file2 
mv $TMP/tempfile "$file2"
# removing the temporary folder if tempfile is not existent anymore
[ -e $TMP/tempfile ] && echo "Error!" || rm -r $TMP

De Bash-Manual # Shell-Parameters :

Un paramètre est une entité qui stocke des valeurs. Il peut s'agir d'un nom, d'un numéro ou de l'un des caractères spéciaux répertoriés ci-dessous. Une variable est un paramètre désigné par un nom. Une variable a une valeur et zéro ou plusieurs attributs. Les attributs sont attribués à l'aide de la commande declare builtin (voir la description de la déclaration dans les Bash Builtins).

Un paramètre est défini si une valeur lui a été attribuée. La chaîne nulle est une valeur valide. Une fois qu'une variable est définie, elle peut être désactivée uniquement à l'aide de la commande intégrée unset.

Une variable peut être assignée à un énoncé de la forme

name = [ valeur ]

Et si vous voulez lire les noms de fichiers à partir d’un dialogue interactif:

#!/bin/bash

# creating a temporary directory to prevent overwrites if the file called 'tmpfile' exists
TMP=$(mktemp -d)

# reading the filenames from user input into variables for later use
read -rp "Enter first filename: " file1
read -rp "Enter second filename: " file2

# swapping the files
# first move and rename file1 to $TMP/tempfile
mv "$file1" $TMP/tempfile
# renaming file2 to file1 name
mv "$file2" "$file1"
# now renaming and moving the tempfile to file2 
mv $TMP/tempfile "$file2"
# removing the temporary folder if tempfile is not existent anymore
[ -e $TMP/tempfile ] && echo "Error!" || rm -r $TMP

De Bash-Manual # Bash-Builtins :

read [-ers] [-a aname] [-d delim] [-i text] [-n nchars]
    [-N nchars] [-p Prompt] [-t timeout] [-u fd] [name …]

Une ligne est lue à partir de l'entrée standard ou du descripteur de fichier fd fourni en tant qu'argument de l'option -u, divisé en mots en tant que décrit ci-dessus dans [Fractionnement des mots] [6], et le premier mot est attribué au premier nom , le deuxième mot au deuxième nom , etc. S'il y a plus de mots que de noms, les mots restants et leurs délimiteurs intermédiaires sont attribués au dernier nom . S'il y a moins de mots lus dans le flux d'entrée que de noms, les noms restants se voient attribuer des valeurs vides. Les caractères de la valeur de la variable IFS servent à diviser la ligne en mots en utilisant les mêmes règles que le shell utilise pour le développement (décrites ci-dessus dans [Fractionnement de mots] [6]). Le caractère de barre oblique inversée '\' peut être utilisé pour supprimer toute signification particulière du prochain caractère lu et du maintien de la ligne. Si aucun nom n'est fourni, la ligne lue est affectée à la variable REPLY.

read accepte plusieurs options. Dans ce cas, deux sont les plus pertinents, car vous souhaitez poser une question à l'utilisateur et obtenir ses commentaires. Ces options sont:

  • -r → Si cette option est donnée, la barre oblique inverse ne fait pas office de caractère d'échappement. La barre oblique inverse est considérée comme faisant partie de la ligne. En particulier, une paire backslash-newline ne peut pas être utilisée comme continuation de ligne.
  • -p Prompt → Afficher Invite , sans nouvelle ligne, avant d'essayer de lire une entrée. L'invite ne s'affiche que si l'entrée provient d'un terminal.

Bien que ce ne soit pas la pire situation à oublier -r, vous souhaitez presque toujours l’inclure pour empêcher \ d’agir en tant que caractère d'échappement. -p montre à l'utilisateur une invite.

6
Videonauth