Comment arrêter la commande de recherche après le premier match?

131

Existe-t-il un moyen de forcer la findcommande à s’arrêter juste après avoir trouvé le premier match?

coffeMug
la source
4
TheUnseen: La raison pour laquelle il se ferme après cinq résultats lorsqu'il est connecté à head -n 5 est parce que head se ferme après cinq résultats. Lorsque la tête sort de la conduite, le tuyau se ferme et envoie un signal au programme qui s'y connecte pour se terminer également. Désolé de ne pas vous répondre directement, apparemment, vous avez besoin de 50 points de réputation pour répondre.
Ruste

Réponses:

148

Avec GNU ou FreeBSD find, vous pouvez utiliser le -quitprédicat:

find . ... -print -quit

L' findéquivalent NetBSD :

find . ... -print -exit

Si vous ne faites qu'imprimer le nom, et en supposant que les noms de fichiers ne contiennent pas de caractères de nouvelle ligne, vous pouvez procéder comme suit:

find . ... -print | head -n 1

Cela ne s'arrêtera pas findaprès le premier match, mais éventuellement, en fonction du moment choisi et de la mise en mémoire tampon lors du deuxième match ou beaucoup (beaucoup) plus tard. Fondamentalement, findse terminera par un SIGPIPE quand il essaie de sortir quelque chose alors qu'il headest déjà parti car il a déjà lu et affiché la première ligne d'entrée.

Notez que tous les shells n'attendront pas cette findcommande après leur headretour. Les implémentations de Bourne Shell et d’AT & T ksh(non interactif) et yash(seulement si ce pipeline est la dernière commande d’un script) ne le feraient pas, le laissant fonctionner en arrière-plan. Si vous préférez voir ce comportement dans n’importe quel shell, vous pouvez toujours changer ce qui précède pour:

(find . ... -print &) | head -n 1

Si vous faites plus qu'imprimer les chemins des fichiers trouvés, vous pouvez essayer cette approche:

find . ... -exec sh -c 'printf "%s\n" "$1"; kill "$PPID"' sh {} \;

(remplacez printfpar ce que vous feriez avec ce fichier).

Cela a pour effet secondaire de findretourner un statut de sortie reflétant le fait qu'il a été tué cependant.

En fait, utiliser le signal SIGPIPE au lieu de SIGTERM ( kill -s PIPEau lieu de kill) fera que certains shells seront plus silencieux à propos de cette mort (mais renverront tout de même un statut de sortie non nul).

Stéphane Chazelas
la source
3
Dans le cas où quelqu'un aurait besoin de vérifier si un fichier correspond aux prédicats, s'arrêtant dès qu'on en trouve un dans Bash et GNU Find, vous pouvez le faire: if [[ $(find ... -print -quit) ]]; then ...Il teste simplement si rien n'est imprimé.
Tobia
@Tobia Mieux vaut mettre la $(…)partie entre guillemets au cas où vous n'utiliseriez que les crochets simples ( [ … ]).
phk
@phk Sauf que je n'utilise pas les crochets simples (parce qu'ils sont horribles), je n'ai donc pas besoin d'utiliser des guillemets.
Tobia
2
@Tobia, [est une commande standard. Ce n'est pas tellement cette commande qui est horrible, mais la façon dont les obus Bourne-like analysent les lignes de commande. [[...]]est une construction ksh qui a ses propres problèmes dans divers shells. Par exemple, jusqu'à récemment [[ $(...) ]], ne travaillait pas zsh(vous aviez besoin de [[ -n $(...) ]]). Sauf dans zsh, vous avez besoin de guillemets [[ $a = $b ]], il y [[ =~ ]]a des différences incompatibles entre les implémentations et même entre les versions de bash et plusieurs bugs dans certaines. Personnellement, je préfère [.
Stéphane Chazelas
c'est quoi ...? .
kyb
11
find . -name something -print -quit

Termine find après le premier match après l’avoir imprimé.

Mettez fin à la recherche après un nombre spécifique de correspondances et d’impression des résultats:

find . -name something -print | head -n 5

Curieusement, head termine maintenant la chaîne après 5 matchs, bien que je ne sache pas comment ni pourquoi.

C'est très facile à tester. Il suffit de laisser chercher une racine qui produirait des milliers, voire davantage de correspondances en prenant au moins une minute ou plus. Mais lorsque vous êtes connecté à "head", "find" se terminera après le nombre spécifié de lignes définies dans head (la tête par défaut indique 10, utilisez "head -n" pour spécifier des lignes).

Notez que cela se terminera lorsque "head -n" aura atteint le nombre de caractères de nouvelle ligne spécifié. Par conséquent, toute correspondance contenant plusieurs caractères de nouvelle ligne comptera en conséquence.

L'invisible
la source
J'ai également observé que ce programme se termine une fois que la tête est terminée avec son phénomène de sortie, mais pas de manière uniforme d'un shell à l'autre. Je pense que cela mérite sa propre question - heureusement pour bash, la réponse est déjà donnée par le comportement Bash: Head & Tail de StackOverflow avec le script bash . Cela me donne suffisamment d’informations pour conclure que la réponse du programme à SIGPIPE dépend de sa réponse à SIGPIPE, qu’il s’agisse de l’arrêt ou de la poursuite de l’exécution en arrière-plan.
Sage
Je voulais dire «entre * programmes * / coquilles», mais apparemment, unix.stackexchange.com préférerait que je consigne cela en tant que second commentaire plutôt que de me laisser éditer mon premier commentaire (il s'agit d'une décision de stratégie stackexchange, spécifique à un site). En outre, je vois maintenant que @Ruste a commenté cet effet en haut, ce qui ne m'a pas aidé initialement car je suis allé directement aux réponses ...
sage
2

À des fins de divertissement, voici un générateur de recherche paresseux à Bash. Cet exemple génère un anneau sur les fichiers du répertoire en cours. Lire autant que vous voulez alors kill %+(peut-être juste 1)

#!/usr/bin/env bash
unset -v files n
trap 'kill "$x_PID"' EXIT

coproc x while :; do
    find . -type f -maxdepth 1 -exec sh -c "$(</dev/fd/3)" _ {} +
done 4<&0 <<\EOF 3<&0 <&4-
for x; do
    read -r _
    printf '%s\0' "$x"
done
EOF

while
    echo >&${x[1]}
    IFS= read -rd '' -u "$x" 'files[n++]'
do
    printf '%q ' "${files[@]}"
    echo
    sleep .2
done
ormaaj
la source
1

grep retourne également si utilisé avec le drapeau -m, donc avec

find stuff | grep -m1 .

il reviendra après la première ligne imprimée par find.

La différence entre ceci et ceci find stuff -print -quit | head -1est que si la recherche est assez rapide, grep pourrait ne pas être en mesure d'arrêter le processus à temps (cela n'a pas vraiment d'importance), alors que si la recherche est longue, elle épargnera beaucoup d'impressions. lignes.

cela fonctionne à la place avec busybox find, bien que puisque busybox, grep dispose également de -mcela, ce n'est pas vraiment nécessaire

find /tmp/stuff -exec "sh" "-c" "eval 'echo {}; { kill \$PPID; }'" \;

cela crachera un message sur le processus de recherche ayant reçu le signal (généralement) sigterm, mais cette sortie appartient au shell en cours d'exécution, pas à la commande find, elle ne gâche donc pas le résultat de la commande, ce qui signifie que les tuyaux ou les redirections ne produiront que la ligne. assorti par trouver.

untore
la source