Le TAB
caractère est un caractère de contrôle qui, lorsqu'il est envoyé à un terminal¹, fait passer le curseur du terminal au prochain taquet de tabulation. Par défaut, dans la plupart des terminaux, les taquets de tabulation sont espacés de 8 colonnes, mais c'est configurable.
Vous pouvez également avoir des taquets de tabulation à intervalles irréguliers:
$ tabs 3 9 11; printf '\tx\ty\tz\n'
x y z
Seul le terminal sait combien de colonnes à droite un TAB déplacera le curseur.
Vous pouvez obtenir ces informations en interrogeant la position du curseur depuis le terminal avant et après l'envoi de l'onglet.
Si vous souhaitez effectuer ce calcul à la main pour une ligne donnée et en supposant que cette ligne est imprimée dans la première colonne de l'écran, vous devrez:
- savoir où sont les taquets de tabulation²
- connaître la largeur d'affichage de chaque caractère
- connaître la largeur de l'écran
- décidez si vous voulez gérer d'autres caractères de contrôle comme
\r
(qui déplace le curseur vers la première colonne) ou \b
qui ramène le curseur en arrière ...)
Cela peut être simplifié si vous supposez que les taquets de tabulation sont toutes les 8 colonnes, la ligne tient dans l'écran et il n'y a pas d'autres caractères de contrôle ou caractères (ou non-caractères) que votre terminal ne peut pas afficher correctement.
Avec GNU wc
, si la ligne est stockée dans $line
:
width=$(printf %s "$line" | wc -L)
width_without_tabs=$(printf %s "$line" | tr -d '\t' | wc -L)
width_of_tabs=$((width - width_without_tabs))
wc -L
donne la largeur de la ligne la plus large dans son entrée. Il le fait en utilisant wcwidth(3)
pour déterminer la largeur des caractères et en supposant que les taquets de tabulation sont toutes les 8 colonnes.
Pour les systèmes non GNU, et avec les mêmes hypothèses, voir l'approche de @ Kusalananda . C'est encore mieux car il vous permet de spécifier les taquets de tabulation mais malheureusement ne fonctionne actuellement pas avec GNU expand
(au moins) lorsque l'entrée contient des caractères multi-octets ou 0-largeur (comme la combinaison de caractères) ou des caractères double-largeur.
¹ Notez cependant que si vous le faites stty tab3
, la discipline de ligne de périphérique tty prendra en charge le traitement des onglets (convertira TAB en espaces en fonction de sa propre idée de l'endroit où le curseur pourrait être avant d'envoyer au terminal) et implémentera des arrêts de tabulation toutes les 8 colonnes. Test sous Linux, il semble gérer correctement les caractères CR, LF et BS ainsi que les caractères UTF-8 multi-octets (fournis iutf8
également), mais c'est à peu près tout. Il suppose que tous les autres caractères non contrôlés (y compris les caractères à largeur nulle et à double largeur) ont une largeur de 1, il (évidemment) ne gère pas les séquences d'échappement, ne s'emballe pas correctement ... Cela est probablement destiné aux terminaux qui impossible de traiter les onglets.
Dans tous les cas, la discipline de ligne tty a besoin de savoir où se trouve le curseur et utilise ces heuristiques ci-dessus, car lorsque vous utilisez l' icanon
éditeur de ligne (comme lorsque vous entrez du texte pour des applications comme cat
celles-ci n'implémentent pas leur propre éditeur de ligne), lorsque vous appuyez sur TabBackspace, la discipline de ligne doit savoir combien de caractères BS envoyer pour effacer ce caractère de tabulation à afficher. Si vous changez l'emplacement des taquets de tabulation (comme avec tabs 12
), vous remarquerez que les tabulations ne sont pas effacées correctement. Idem si vous entrez des caractères double largeur avant d'appuyer sur TabBackspace.
² Pour cela, vous pouvez envoyer des caractères de tabulation et interroger la position du curseur après chacun. Quelque chose comme:
tabs=$(
saved_settings=$(stty -g)
stty -icanon min 1 time 0 -echo
gawk -vRS=R -F';' -vORS= < /dev/tty '
function out(s) {print s > "/dev/tty"; fflush("/dev/tty")}
BEGIN{out("\r\t\33[6n")}
$NF <= prev {out("\r"); exit}
{print sep ($NF - 1); sep=","; prev = $NF; out("\t\33[6n")}'
stty "$saved_settings"
)
Ensuite, vous pouvez utiliser cela comme expand -t "$tabs"
utilisant la solution de @ Kusalananda.
x
) avant d'appelerexpand
autrement, vous compteriez également les espaces qui étaient initialement dans l'entrée également.