web-dev-qa-db-fra.com

Bash - dessinez une ligne verticale derrière des lignes de longueur variable

J'ai un fichier texte qui a le format suivant et je veux ajouter une ligne verticale après ces lignes, suivie d'une augmentation des nombres:

c4-1 d e c
c d e c
e-2 f g2
e4 f g2
g8-4\( a-5 g f\) e4 c
g'8\( a g f\) e4 c
c-1 r c2
c4 r c2 

J'obtiens la ligne et la numérotation avec le while-loop:

#!/bin/bash

while read -r line; do
    if [ -z "$line" ]; then
        echo
        continue
    fi
    n=$((++n)) \
    && grep -vE "^$|^%" <<< "$line" \
    | sed 's/$/\ \|\ \%'$(("$n"))'/'
done < file

et obtenez une sortie comme:

c4-1 d e c | %1
c d e c | %2
e-2 f g2 | %3
e4 f g2 | %4
g8-4\( a-5 g f\) e4 c | %5
g'8\( a g f\) e4 c | %6
c-1 r c2 | %7
c4 r c2 | %8

maintenant je veux que l'addition soit alignée verticalement et obtienne une sortie comme celle-ci:

c4-1 d e c            | %1
c d e c               | %2
e-2 f g2              | %3
e4 f g2               | %4
g8-4\( a-5 g f\) e4 c | %5
g'8\( a g f\) e4 c    | %6
c-1 r c2              | %7
c4 r c2               | %8

cela signifierait que je dois en quelque sorte obtenir la longueur de ligne de la ligne la plus longue (ici: 21 caractères) et la longueur de ligne de chaque ligne et ajouter la différence avec les espaces, comment pourrais-je y parvenir?

9
nath

Vous pouvez imprimer les lignes sans alignement et formater la sortie avec column -t et un caractère de délimiteur factice:

#!/bin/bash

while read -r line; do
  if [ -z "$line" ]; then
    echo
    continue
  fi
  printf '%s@| %%%s\n' "$line" "$((++n))"
done < file | column -e -s'@' -t | sed 's/ |/|/'

Ici, j'ai ajouté un @ comme caractère fictif avant le | indiquant la fin de la colonne. La commande sed à la fin est utilisée pour supprimer un caractère d'espace supplémentaire avant le |. Option -e est nécessaire pour conserver les lignes vides dans la sortie.

Production:

c4-1 d e c            | %1
c d e c               | %2
e-2 f g2              | %3
e4 f g2               | %4
g8-4\( a-5 g f\) e4 c | %5
g'8\( a g f\) e4 c    | %6
c-1 r c2              | %7
c4 r c2               | %8
10
Freddy

Utilisation de awk + GNU wc en supposant que tous les caractères de l'entrée sont à simple largeur:

$ awk -v f="$(wc -L < ip.txt)" '{printf "%-*s | %%%s\n", f, $0, NR}' ip.txt
c4-1 d e c            | %1
c d e c               | %2
e-2 f g2              | %3
e4 f g2               | %4
g8-4\( a-5 g f\) e4 c | %5
g'8\( a g f\) e4 c    | %6
c-1 r c2              | %7
c4 r c2               | %8
9
Sundeep

Bash simple: fonctionne avec la version bash> = 4.0

#!/bin/bash
mapfile -t lines < file
max=0
for line in "${lines[@]}"; do
    max=$(( ${#line} > max ? ${#line} : max ))
done
for i in "${!lines[@]}"; do
    printf "%-*s | %%%d\n" $max "${lines[i]}" $((i+1))
done

Pour les anciennes versions de bash, remplacez mapfile par une boucle while-read: cela fonctionne avec la version 3.2

#!/bin/bash
lines=()
max=0
while IFS= read -r line || [[ -n "line" ]]; do
    lines+=("$line")
    max=$(( ${#line} > max ? ${#line} : max ))
done < file
for i in "${!lines[@]}"; do
    printf "%-*s | %%%d\n" $max "${lines[i]}" $((i+1))
done
4
glenn jackman

juste pour les enregistrements: (c'est horriblement lent, mais c'était ma première tentative en utilisant wc -L)
va certainement chercher la réponse de @Freddy en utilisant column!

#!/bin/bash

file="$1"

ll=$(wc -L < "$file")

while read -r line; do
    if [ -z "$line" ]; then
        echo
        continue
    fi
    sl=$(wc -L <<< "$line")
    if [ "$ll" = "$sl" ]; then
        as=$(echo "$ll - $sl" | bc)
    else
        as=$(echo "$ll - $sl + 1" | bc)
    fi
    space=$(printf '\ %.0s' $(seq "$as") )
    n=$((++n)) \
    && grep -vE "^$|^%" <<< "$line" \
    | sed "s/$/$space\ \|\ \%$(printf "%s" "$n")/"
done < "$file"

bien qu'il fonctionne avec un espace supplémentaire:

c4-1 d e c             | %1
c d e c                | %2
e-2 f g2               | %3
e4 f g2                | %4
g8-4\( a-5 g f\) e4 c  | %5
g'8\( a g f\) e4 c     | %6
c-1 r c2               | %7
c4 r c2                | %8
2
nath

En supposant qu'il n'y ait pas de @ caractères dans les données (remplacez simplement les deux @ utilisé ici avec un autre caractère dans ce cas):

$ awk -v OFS='@| %' '{ print $0, FNR }' file | column -s '@' -t
c4-1 d e c             | %1
c d e c                | %2
e-2 f g2               | %3
e4 f g2                | %4
g8-4\( a-5 g f\) e4 c  | %5
g'8\( a g f\) e4 c     | %6
c-1 r c2               | %7
c4 r c2                | %8

Cela utilise la chaîne @| % comme séparateur de champ de sortie et imprime l'entrée suivie du numéro de ligne de chaque ligne (séparés par ce séparateur), puis utilise column pour l'aligner sur le @ caractères (ceux-ci seront supprimés).


Si vous aimez sed ou les expressions régulières maladroites, vous pouvez toujours numéroter les lignes avec cat -n ou nl -b a, puis déplacez les numéros de ligne à la fin de la ligne et insérez @| % en utilisant sed, avant d'appeler column:

$ cat -n file | sed -E 's/^[[:blank:]]*([[:digit:]]+)[[:blank:]]*(.*)$/\2@| \%\1/' | column -s '@' -t
c4-1 d e c             | %1
c d e c                | %2
e-2 f g2               | %3
e4 f g2                | %4
g8-4\( a-5 g f\) e4 c  | %5
g'8\( a g f\) e4 c     | %6
c-1 r c2               | %7
c4 r c2                | %8

Utilisez awk pour lire votre fichier deux fois, une fois pour déterminer la longueur maximale de ligne (m) et une nouvelle fois pour formater les lignes à cette longueur. column n'est pas utilisé ici (ou dans la dernière solution):

$ awk 'FNR==NR { m=(length>m?length:m); next } { printf("%-*s | %%%d\n", m, $0, FNR) }' file file
c4-1 d e c            | %1
c d e c               | %2
e-2 f g2              | %3
e4 f g2               | %4
g8-4\( a-5 g f\) e4 c | %5
g'8\( a g f\) e4 c    | %6
c-1 r c2              | %7
c4 r c2               | %8

Notez que le nom de fichier est donné deux fois sur la ligne de commande.


Comme ci-dessus, mais en stockant le fichier en mémoire sous forme de tableau (a) et en l'imprimant en fonction de la longueur de ligne la plus longue à la fin. L'accès au disque bénéficie de la diminution de la consommation mémoire:

$ awk '{ a[FNR]=$0; m=(length>m?length:m) } END { for (i=1; i<=FNR; ++i) printf("%-*s | %%%d\n", m, a[i], i) }' file
c4-1 d e c            | %1
c d e c               | %2
e-2 f g2              | %3
e4 f g2               | %4
g8-4\( a-5 g f\) e4 c | %5
g'8\( a g f\) e4 c    | %6
c-1 r c2              | %7
c4 r c2               | %8
2
Kusalananda