La commande shell suivante était censée imprimer uniquement les lignes impaires du flux d'entrée:
echo -e "aaa\nbbb\nccc\nddd\n" | (while true; do head -n 1; head -n 1 >/dev/null; done)
Mais au lieu il imprime juste la première ligne: aaa
.
La même chose ne se produit pas lorsqu'elle est utilisée avec l' option -c
( --bytes
):
echo 12345678901234567890 | (while true; do head -c 5; head -c 5 >/dev/null; done)
Cette commande sort 1234512345
comme prévu. Mais cela ne fonctionne que dans l' implémentation coreutils de l' head
utilitaire. L' implémentation de busybox mange toujours des caractères supplémentaires, donc la sortie est juste 12345
.
Je suppose que ce mode de mise en œuvre spécifique est effectué à des fins d'optimisation. Vous ne pouvez pas savoir où se termine la ligne, vous ne savez donc pas combien de caractères vous devez lire. La seule façon de ne pas consommer de caractères supplémentaires dans le flux d'entrée est de lire le flux octet par octet. Mais la lecture du flux un octet à la fois peut être lente. Je suppose donc qu'il head
lit le flux d'entrée dans un tampon suffisamment grand, puis compte les lignes dans ce tampon.
On ne peut pas en dire autant du cas où l' --bytes
option est utilisée. Dans ce cas, vous savez combien d'octets vous devez lire. Vous pouvez donc lire exactement ce nombre d'octets et pas plus que cela. L' implémentation de corelibs utilise cette opportunité, mais pas celle de busybox , elle lit toujours plus d'octets que nécessaire dans un tampon. Cela est probablement fait pour simplifier la mise en œuvre.
Donc la question. Est-il correct que l' head
utilitaire consomme plus de caractères du flux d'entrée qu'il ne lui a été demandé? Existe-t-il une sorte de standard pour les utilitaires Unix? Et s'il y en a un, spécifie-t-il ce comportement?
PS
Vous devez appuyer sur Ctrl+C
pour arrêter les commandes ci-dessus. Les utilitaires Unix n'échouent pas à la lecture au-delà EOF
. Si vous ne voulez pas appuyer, vous pouvez utiliser une commande plus complexe:
echo 12345678901234567890 | (while true; do head -c 5; head -c 5 | [ `wc -c` -eq 0 ] && break >/dev/null; done)
que je n'ai pas utilisé pour plus de simplicité.
la source
Réponses:
Oui, c'est autorisé (voir ci-dessous).
Oui, POSIX volume 3, Shell & Utilities .
Il fait, dans son introduction:
head
est l'un des utilitaires standard , donc une implémentation conforme à POSIX doit implémenter le comportement décrit ci-dessus.GNU
head
ne tentent de quitter le descripteur de fichier dans la bonne position, mais il est impossible de chercher sur les tuyaux, donc dans votre test , il ne parvient pas à rétablir la position. Vous pouvez le voir en utilisantstrace
:Le
read
retourne 17 octets (toutes les entrées disponibles), enhead
traite quatre et essaie ensuite de reculer de 13 octets, mais il ne peut pas. (Vous pouvez également voir ici que GNUhead
utilise un tampon de 8 Ko.)Lorsque vous dites
head
de compter les octets (ce qui n'est pas standard), il sait combien d'octets à lire, il peut donc (s'il est implémenté de cette façon) limiter sa lecture en conséquence. C'est pourquoi votrehead -c 5
test fonctionne: GNUhead
ne lit que cinq octets et n'a donc pas besoin de chercher à restaurer la position du descripteur de fichier.Si vous écrivez le document dans un fichier et que vous l'utilisez à la place, vous obtiendrez le comportement que vous recherchez:
la source
line
(maintenant supprimés de POSIX / XPG mais toujours disponibles sur de nombreux systèmes) ouread
(IFS= read -r line
) qui lisent un octet à la fois pour éviter le problème.head -c 5
lecture de 5 octets ou d'un tampon complet dépend de l'implémentation (notez également que cehead -c
n'est pas standard), vous ne pouvez pas vous fier à cela. Vous devezdd bs=1 count=5
avoir la garantie que pas plus de 5 octets seront lus.-c 5
description.head
intégréeksh93
lit un octet à la foishead -n 1
lorsque l'entrée n'est pas recherchée.dd
ne fonctionne correctement qu'avec les canaux avecbs=1
si vous utilisez uncount
as reads sur les canaux peut retourner moins que demandé (mais au moins un octet à moins que eof ne soit atteint). GNUdd
aiflag=fullblock
qui peut atténuer ce bien.de POSIX
Il ne dit rien sur la quantité de données à
head
lire depuis l'entrée. Exiger qu'il soit lu octet par octet serait idiot, car il serait extrêmement lent dans la plupart des cas.Ceci est, cependant, traité dans l'
read
utilitaire intégré /: tous les shells que je peux trouverread
dans les tuyaux un octet à la fois et le texte standard peut être interprété comme signifiant que cela doit être fait, pour pouvoir lire uniquement une seule ligne:Dans le cas de
read
, qui est utilisé dans les scripts shell, un cas d'utilisation courant serait quelque chose comme ceci:Ici, l'entrée standard de
someprogram
est la même que celle du shell, mais on peut s'attendresomeprogram
à ce que tout ce qui vient après la première ligne d'entrée consommée par leread
et non ce qui reste après une lecture tamponnée puisse être luread
. En revanche, utiliserhead
comme dans votre exemple est beaucoup plus rare.Si vous voulez vraiment supprimer toutes les autres lignes, il serait préférable (et plus rapide) d'utiliser un outil capable de gérer l'intégralité de l'entrée en une seule fois, par exemple
la source
-r
,read
peut lire plusieurs lignes (sansIFS=
cela, il supprimerait également les espaces et les tabulations de début et de fin (avec la valeur par défaut de$IFS
)).head
intégréeksh93
lit un octet à la foishead -n 1
lorsque l'entrée n'est pas recherchée.la source