Utilisez basename pour analyser une liste de chemins contenus dans un fichier

9

J'utilise Mac OSX et j'essaie d'utiliser la ligne de commande pour trouver le nombre de fichiers que j'ai avec le même nom.

J'ai essayé d'utiliser la commande suivante:

find ~ -type f -name "*" -print | basename | sort | uniq -d > duplicate_files

Ça ne marche pas! Quand je fais ce qui suit:

find ~ -type f -name "*" -print > duplicate_files

Ensuite, duplicate_files contient les chemins de tous mes fichiers. Je pense donc que le problème est avec basename- il n'accepte pas l'entrée standard. J'ai ensuite essayé ce qui suit:

basename $(find ~ -type f -name "*" -print) > duplicate_files

mais encore une fois cela ne semble pas fonctionner. La recherche sur Internet ne semble pas apporter beaucoup de joie. Toutes les pensées les bienvenues.

JohnB
la source

Réponses:

16

basename fonctionne sur son argument de ligne de commande, il ne lit pas depuis l'entrée standard.

Vous n'avez pas besoin d'appeler l' basenameutilitaire, et vous feriez mieux de ne pas le faire: tout ce qu'il ferait serait de retirer la partie avant la dernière /, et il serait lent d'appeler une commande externe pour chaque entrée, vous pouvez utiliser un traitement de texte utilitaire à la place.

find ~ -type f | sed 's!.*/!!' | sort | uniq -d

Il peut être plus utile de garder une trace de l'emplacement des fichiers. Le tri par nom facilite la recherche des doublons, mais sortn'a pas la possibilité d'utiliser le dernier champ. Ce que vous pouvez faire est de copier le dernier /champ séparé au début, puis de trier, puis d'utiliser un peu de traitement awk ad hoc pour extraire et présenter les doublons.

find ~ -type f |
sed 's!.*/\(.*\)!\1/&!' |   # copy the last field to the beginning
sort -t/ -k1,1 |
cut -d/ -f2- |   # remove the extra first field (could be combined with awk below)
awk -F / '{
    if ($NF == name) {
        if (previous != "") {print previous; previous = ""}
        print
    } else {
        previous = $0
        name = $NF
    }
'

(Notez que je suppose qu'aucun de vos noms de fichiers ne contient de caractères de nouvelle ligne.)

Gilles 'SO- arrête d'être méchant'
la source
Merci beaucoup. C'est exactement ce que j'essayais de faire ... très utile
JohnB
7

Pourquoi ne pas utiliser les fonctionnalités findintégrées pour afficher uniquement le nom de fichier:

find ~ -type f -printf '%f\n' | sort | uniq -c

(suppose GNU find) ou au moins quelque chose comme ça:

find ~ -exec basename {} \; | sort | uniq -c

basename impossible de lire via un canal ou de traiter plusieurs fichiers à la fois.

ps. Il n'est pas nécessaire de spécifier -name '*'si vous souhaitez répertorier tous les fichiers. Il s'agit d'une option par défaut.

se ruer
la source
Merci - '-printf' ne fonctionne pas pour OS X UNIX
JohnB
Et quand j'essaye la deuxième version que j'obtiens basename: unknown primary or operator. Merci pour l'astuce-name "*"
JohnB
C'est étrange. Je peux voir -printfmême dans la page de manuel posix. À propos de l'erreur avec la deuxième façon, c'est la cause d'une faute de frappe dans ma réponse. Fixé. Pourriez-vous s'il vous plaît l'essayer une fois de plus?
rush
Aussi avec -printfje reçois le -printf: unknown primary or operator. De plus, lorsque j'ai vérifié l'Unix dans un livre de référence Nutshell, il est répertorié comme une option GNU / Linux - ne dit rien sur OSX
JohnB
1
En fait, la meilleure source serait man finddans votre console :)
précipitez-vous
4

Cela semble fonctionner pour moi sur OSX:

find ~ -type f -exec basename -a {} + | sort | uniq -d
rahmu
la source
Oui - c'est un grand merci - par intérêt qu'est-ce que cela +signifie dans la commande?
JohnB
2
Si cela est utile, veuillez envisager de le voter.
suspectus
C'est - je ne peux pas voter parce que j'ai besoin de 15
points de
@StephaneChazelas: Selon la page de manuel du nom de base BSD , l'exécutable peut prendre plusieurs chaînes comme arguments. J'ai revérifié sur OSX, ça marche.
rahmu
1
Très bien désolé, je me corrige. Je n'étais pas au courant de cette extension BSD. Cependant, cela échoue toujours s'il y a exactement deux fichiers. Vous devez également ajouter l' -aoption pour couvrir ce cas.
Stéphane Chazelas
2

Alternatives (suppose qu'il n'y a pas de nouvelle ligne dans les noms de fichiers):

find ~ -type f | awk -F/ '{print $NF}' | sort | uniq -d
Stéphane Chazelas
la source
2

Vous pouvez utiliser xargsavec basenamepour obtenir la sortie souhaitée, comme ceci:

find ~ -type f -name "*" -print | xargs -l basename | sort | uniq -d > duplicate_files
Seff
la source
0

Avec une version récente bashqui gère les tableaux associatifs, les éléments suivants gèrent en outre les chemins d'accès avec des retours à la ligne intégrés:

#!/bin/bash

topdir=$HOME

shopt -s globstar  # enable the ** glob

declare -A count

# count the number of times each filename (base name) occurs
for pathname in "$topdir"/**; do
    # skip names that are not regular files (or not symbolic links to such files)
    [ ! -f "$pathname" ] && continue

    # get the base name
    filename=${pathname##*/}

    # add one to this base name's count
    count[$filename]=$(( ${count[$filename]} + 1 ))
done

# go through the collected names and print any name that
# has a count greater than one
for filename in "${!count[@]}"; do
    if [ "${count[$filename]}" -gt 1 ]; then
        printf 'Duplicate filename: %s\n' "$filename"
    fi
done

Cela n'utilise aucun utilitaire externe.

Kusalananda
la source