linux for - Le plus court moyen d'échanger deux fichiers en bash




commande pdf (13)

$ mv old tmp && mv curr old && mv tmp curr

est légèrement plus efficace!

Enveloppé dans une fonction shell réutilisable:

function swap()         
{
    local TMPFILE=tmp.$$
    mv "$1" $TMPFILE && mv "$2" "$1" && mv $TMPFILE "$2"
}

Deux fichiers peuvent-ils être échangés en bash?

Ou, peuvent-ils être échangés d'une manière plus courte que celle-ci:

cp old tmp
cp curr old
cp tmp curr
rm tmp

J'ai ceci dans un script de travail que j'ai livré. C'est écrit comme une fonction, mais vous l'invoqueriez

d_swap lfile rfile

GNU mv a les commutateurs -b et -T. Vous pouvez utiliser des répertoires à l’aide du commutateur -T.

Les citations sont pour les noms de fichiers avec des espaces.

C'est un peu bavard, mais je l'ai utilisé plusieurs fois avec des fichiers et des répertoires. Dans certains cas, vous voudrez peut-être renommer un fichier avec le nom d'un répertoire, mais cela n'est pas géré par cette fonction.

Ce n'est pas très efficace si tout ce que vous voulez faire est de renommer les fichiers (en les laissant là où ils sont), c'est mieux avec une variable shell.

d_swap() {
 test $# -eq 2 || return 2

 test -e "$1" || return 3
 test -e "$2" || return 3

 if [ -f "$1" -a -f "$2" ]
 then
    mv -b "$1" "$2" && mv "$2"~ "$1"
    return 0
 fi

 if [ -d "$1" -a -d "$2" ]
 then
    mv -T -b "$1" "$2" && mv -T "$2"~ "$1"
    return 0
 fi

 return 4
}

Cette fonction va renommer les fichiers. Il utilise un nom temporaire (il place un point "." Devant le nom) au cas où les fichiers / répertoires se trouvent dans le même répertoire, ce qui est généralement le cas.

d_swapnames() {
    test $# -eq 2 || return 2

    test -e "$1" || return 3
    test -e "$2" || return 3

    local lname="$(basename "$1")"
    local rname="$(basename "$2")"

    ( cd "$(dirname "$1")" && mv -T "$lname" ".${rname}" ) && \
    ( cd "$(dirname "$2")" && mv -T "$rname" "$lname" ) && \
    ( cd "$(dirname "$1")" && mv -T ".${rname}" "$rname" )
}

C'est beaucoup plus rapide (il n'y a pas de copie, il suffit de renommer). C'est encore plus laid. Et tout sera renommé: fichiers, répertoires, pipes, périphériques.


mv old tmp
mv curr old
mv tmp curr

voulez-vous réellement les échanger? Je pense qu'il vaut la peine de mentionner que vous pouvez sauvegarder automatiquement un fichier écrasé avec mv:

mv new old -b

tu auras:

old and old~

si vous voulez avoir

old and old.old

vous pouvez utiliser -S pour changer ~ en votre suffixe personnalisé

mv new old -b -S .old
ls
old old.old

En utilisant cette approche, vous pouvez réellement les échanger plus rapidement, en utilisant seulement 2 mv:

mv new old -b && mv old~ new

Un problème que j'ai rencontré lors de l’utilisation de l’une des solutions fournies ici: vos noms de fichiers seront modifiés.

J'ai incorporé l'utilisation de basename et dirname pour conserver les noms de fichier intacts *.

swap() {
    if (( $# == 2 )); then
        mv "$1" /tmp/
        mv "$2" "`dirname $1`"
        mv "/tmp/`basename $1`" "`dirname $2`"
    else
        echo "Usage: swap <file1> <file2>"
        return 1
    fi
}

J'ai testé cela en bash et zsh.

* Donc, pour clarifier en quoi c'est mieux:

Si vous commencez avec:

dir1/file2: this is file2
dir2/file1: this is file1

Les autres solutions aboutiraient à:

dir1/file2: this is file1
dir2/file1: this is file2

Le contenu est échangé mais les noms de fichiers sont restés . Ma solution le rend:

dir1/file1: this is file1
dir2/file2: this is file2

Le contenu et les noms sont échangés.


En combinant les meilleures réponses, je mets ceci dans mon ~ / .bashrc:

function swap()
{
  tmpfile=$(mktemp $(dirname "$1")/XXXXXX)
  mv "$1" "$tmpfile" && mv "$2" "$1" &&  mv "$tmpfile" "$2"
}

tmpfile=$(mktemp $(dirname "$file1")/XXXXXX)
mv "$file1" "$tmpfile"
mv "$file2" "$file1"
mv "$tmpfile" "$file2"

utiliser mv signifie que vous avez une opération de moins, que vous n'avez pas besoin de la dernière version, mais que mv ne fait que modifier les entrées du répertoire, vous n'utilisez donc pas d'espace disque supplémentaire pour la copie.

Temptationh consiste alors à implémenter une fonction shell swap () ou autre. Si vous faites extrêmement attention à vérifier les codes d'erreur. Pourrait être horriblement destructeur. Il faut également vérifier s'il existe un fichier tmp préexistant.


Une version quelque peu durcie qui fonctionne à la fois pour les fichiers et les répertoires:

function swap()
{
  if [ ! -z "$2" ] && [ -e "$1" ] && [ -e "$2" ] && ! [ "$1" -ef "$2" ] && (([ -f "$1" ] && [ -f "$2" ]) || ([ -d "$1" ] && [ -d "$2" ])) ; then
    tmp=$(mktemp -d $(dirname "$1")/XXXXXX)
    mv "$1" "$tmp" && mv "$2" "$1" &&  mv "$tmp"/"$1" "$2"
    rmdir "$tmp"
  else
    echo "Usage: swap file1 file2 or swap dir1 dir2"
  fi
}

Cela fonctionne sous Linux. Pas sûr de OS X.


Voici un script d' swap avec vérification d'erreur paranoïde pour éviter les cas improbables d'échec.

  • si l'une des opérations échoue, c'est signalé.
  • le chemin du premier argument est utilisé pour le chemin temporaire (pour éviter de passer d'un système de fichiers à un autre) .
  • dans le cas peu probable où le second mouvement échoue, le premier est restauré.

Scénario:

#!/bin/sh

if [ -z "$1" ] || [ -z "$2" ]; then
    echo "Expected 2 file arguments, abort!"
    exit 1
fi

if [ ! -z "$3" ]; then
    echo "Expected 2 file arguments but found a 3rd, abort!"
    exit 1
fi

if [ ! -f "$1" ]; then
    echo "File '$1' not found, abort!"
    exit 1
fi

if [ ! -f "$2" ]; then
    echo "File '$2' not found, abort!"
    exit 1
fi

# avoid moving between drives
tmp=$(mktemp --tmpdir="$(dirname '$1')")
if [ $? -ne 0 ]; then
    echo "Failed to create temp file, abort!"
    exit 1
fi

# Exit on error,
mv "$1" "$tmp"
if [ $? -ne 0 ]; then
    echo "Failed to to first file '$1', abort!"
    rm "$tmp"
    exit 1
fi

mv "$2" "$1"
if [ $? -ne 0 ]; then
    echo "Failed to move first file '$2', abort!"
    # restore state
    mv "$tmp" "$1"
    if [ $? -ne 0 ]; then
        echo "Failed to move file: (unable to restore) '$1' has been left at '$tmp'!"
    fi
    exit 1
fi

mv "$tmp" "$2"
if [ $? -ne 0 ]; then
    # this is very unlikely!
    echo "Failed to move file: (unable to restore) '$1' has been left at '$tmp', '$2' as '$1'!"
    exit 1
fi


Ajoutez ceci à votre .bashrc:

function swap()         
{
    local TMPFILE=tmp.$$
    mv "$1" $TMPFILE
    mv "$2" "$1"
    mv $TMPFILE "$2"
}

Si vous voulez gérer les échecs potentiels d'opérations mv intermédiaires, cochez la réponse de Can Bal .

Veuillez noter que ni cela, ni aucune autre réponse ne fournissent une solution atomique , car il est impossible de les implémenter avec des appels système Linux et / ou des systèmes de fichiers Linux courants. Pour le noyau Darwin, vérifiez syscall exchangedata data.


Réécriture d'une answer supprimée par VonC .

La réponse succincte de Robert Gamble traite directement de la question. Celui-ci amplifie sur certains problèmes les noms de fichiers contenant des espaces.

Voir aussi: $ {1: + "$ @"} dans / bin / sh

Thèse de base: "[email protected]" est correct, et $* (non cité) est presque toujours faux. Cela est dû au fait que "[email protected]" fonctionne correctement lorsque les arguments contiennent des espaces et fonctionne de la même manière que $* lorsqu'ils ne le sont pas. Dans certaines circonstances, "$*" est également OK, mais "[email protected]" généralement (mais pas toujours) aux mêmes endroits. Non cité, [email protected] et $* sont équivalents (et presque toujours faux).

Alors, quelle est la différence entre $* , [email protected] , "$*" et "[email protected]" ? Ils sont tous liés à «tous les arguments de la coquille», mais ils font des choses différentes. Quand non cité, $* et [email protected] font la même chose. Ils traitent chaque «mot» (séquence de non-espace) comme un argument séparé. Les formes quotées sont cependant très différentes: "$*" traite la liste d'arguments comme une seule chaîne séparée par un espace, alors que "[email protected]" traite les arguments presque exactement comme ils le sont quand ils sont spécifiés sur la ligne de commande. "[email protected]" transforme en rien quand il n'y a pas d'arguments positionnels; "$*" transforme en une chaîne vide - et oui, il y a une différence, bien que cela puisse être difficile à percevoir. Voir plus d'informations ci-dessous, après l'introduction de la commande (non standard) al .

Thèse secondaire: si vous devez traiter des arguments avec des espaces et les transmettre ensuite à d'autres commandes, vous avez parfois besoin d'outils non standard pour vous aider. (Ou vous devriez utiliser des tableaux, avec précaution: "${array[@]}" se comporte de manière analogue à "[email protected]" .)

Exemple:

    $ mkdir "my dir" anotherdir
    $ ls
    anotherdir      my dir
    $ cp /dev/null "my dir/my file"
    $ cp /dev/null "anotherdir/myfile"
    $ ls -Fltr
    total 0
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 my dir/
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 anotherdir/
    $ ls -Fltr *
    my dir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 my file

    anotherdir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 myfile
    $ ls -Fltr "./my dir" "./anotherdir"
    ./my dir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 my file

    ./anotherdir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 myfile
    $ var='"./my dir" "./anotherdir"' && echo $var
    "./my dir" "./anotherdir"
    $ ls -Fltr $var
    ls: "./anotherdir": No such file or directory
    ls: "./my: No such file or directory
    ls: dir": No such file or directory
    $

Pourquoi ça ne marche pas? Cela ne fonctionne pas car l'interpréteur de commandes traite les citations avant d'étendre les variables. Donc, pour que le shell prenne en compte les citations incluses dans $var , vous devez utiliser eval :

    $ eval ls -Fltr $var
    ./my dir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 my file

    ./anotherdir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 myfile
    $ 

Cela devient vraiment difficile quand vous avez des noms de fichiers tels que " He said, "Don't do this!" " (Avec des guillemets et des guillemets et des espaces).

    $ cp /dev/null "He said, \"Don't do this!\""
    $ ls
    He said, "Don't do this!"       anotherdir                      my dir
    $ ls -l
    total 0
    -rw-r--r--   1 jleffler  staff    0 Nov  1 15:54 He said, "Don't do this!"
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 anotherdir
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 my dir
    $ 

Les coquilles (toutes) ne facilitent pas la manipulation de telles choses, donc (bizarrement) de nombreux programmes Unix ne les traitent pas très bien. Sous Unix, un nom de fichier (un seul composant) peut contenir tous les caractères sauf slash et NUL '\0' . Cependant, les shells n'encouragent aucun espace ou nouvelle ligne ou aucun onglet dans les noms de chemin. C'est aussi pourquoi les noms de fichiers Unix standard ne contiennent pas d'espaces, etc.

Lorsque vous traitez des noms de fichiers qui peuvent contenir des espaces et d'autres caractères gênants, vous devez être extrêmement prudent, et j'ai trouvé il y a longtemps que j'avais besoin d'un programme qui n'est pas standard sur Unix. Je l'appelle escape (la version 1.1 était datée du 1989-08-23T16: 01: 45Z).

Voici un exemple d' escape en cours d'utilisation - avec le système de contrôle SCCS. C'est un script de couverture qui fait à la fois un delta (think check-in ) et un get (think check-out ). Divers arguments, en particulier -y (la raison pour laquelle vous avez effectué le changement) contiendraient des espaces vides et des retours à la ligne. Notez que le script date de 1992, donc il utilise back-ticks au lieu de notation $(cmd ...) et n'utilise pas #!/bin/sh sur la première ligne.

:   "@(#)$Id: delget.sh,v 1.8 1992/12/29 10:46:21 jl Exp $"
#
#   Delta and get files
#   Uses escape to allow for all weird combinations of quotes in arguments

case `basename $0 .sh` in
deledit)    eflag="-e";;
esac

sflag="-s"
for arg in "[email protected]"
do
    case "$arg" in
    -r*)    gargs="$gargs `escape \"$arg\"`"
            dargs="$dargs `escape \"$arg\"`"
            ;;
    -e)     gargs="$gargs `escape \"$arg\"`"
            sflag=""
            eflag=""
            ;;
    -*)     dargs="$dargs `escape \"$arg\"`"
            ;;
    *)      gargs="$gargs `escape \"$arg\"`"
            dargs="$dargs `escape \"$arg\"`"
            ;;
    esac
done

eval delta "$dargs" && eval get $eflag $sflag "$gargs"

(Je n'utiliserais probablement pas l'échappement de manière aussi complète ces jours-ci - ce n'est pas nécessaire avec l'argument -e , par exemple - mais dans l'ensemble, c'est l'un de mes scripts les plus simples utilisant escape .)

Le programme d' escape sort simplement ses arguments, comme le fait echo , mais il s'assure que les arguments sont protégés pour eval (un niveau d' eval , j'ai un programme qui exécute l'exécution de shell à distance, et qui doit échapper à la sortie de escape ).

    $ escape $var
    '"./my' 'dir"' '"./anotherdir"'
    $ escape "$var"
    '"./my dir" "./anotherdir"'
    $ escape x y z
    x y z
    $ 

J'ai un autre programme appelé al qui liste ses arguments un par ligne (et c'est encore plus ancien: version 1.1 datée du 1987-01-27T14: 35: 49). C'est très utile lors du débogage de scripts, car il peut être branché sur une ligne de commande pour voir quels arguments sont réellement passés à la commande.

    $ echo "$var"
    "./my dir" "./anotherdir"
    $ al $var
    "./my
    dir"
    "./anotherdir"
    $ al "$var"
    "./my dir" "./anotherdir"
    $

[ Ajouté: Et maintenant, pour montrer la différence entre les différentes notations "[email protected]" , voici un autre exemple:

$ cat xx.sh
set -x
al [email protected]
al $*
al "$*"
al "[email protected]"
$ sh xx.sh     *      */*
+ al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my file
He
said,
"Don't
do
this!"
anotherdir
my
dir
xx.sh
anotherdir/myfile
my
dir/my
file
+ al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my file
He
said,
"Don't
do
this!"
anotherdir
my
dir
xx.sh
anotherdir/myfile
my
dir/my
file
+ al 'He said, "Don'\''t do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file'
He said, "Don't do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file
+ al 'He said, "Don'\''t do this!"' anotherdir 'my dir' xx.sh anotherdir/myfile 'my dir/my file'
He said, "Don't do this!"
anotherdir
my dir
xx.sh
anotherdir/myfile
my dir/my file
$

Notez que rien ne préserve les espaces d'origine entre * et */* sur la ligne de commande. Notez également que vous pouvez modifier les 'arguments de ligne de commande' dans le shell en utilisant:

set -- -new -opt and "arg with space"

Cela définit 4 options, ' -new ', ' -opt ', ' and ', et ' arg with space '.
]

Hmm, c'est une réponse assez longue - peut-être que l' exégèse est le meilleur terme. Le code source pour l' escape disponible sur demande (email au prénom par le nom de famille sur gmail dot com). Le code source de al est incroyablement simple:

#include <stdio.h>
int main(int argc, char **argv)
{
    while (*++argv != 0)
        puts(*argv);
    return(0);
}

C'est tout. Il est équivalent au script test.sh que Robert Gamble a montré, et pourrait être écrit comme une fonction shell (mais les fonctions shell n'existaient pas dans la version locale de Bourne shell quand j'ai écrit al ).

Notez également que vous pouvez écrire al tant que script shell simple:

[ $# != 0 ] && printf "%s\n" "[email protected]"

Le conditionnel est nécessaire pour qu'il ne produise aucune sortie lorsqu'il n'est pas passé d'arguments. La commande printf produira une ligne vide avec seulement l'argument de chaîne de format, mais le programme C ne produit rien.





linux bash command-line