Commande comme `column -t` qui garde à la place les séparateurs en sortie

17

J'édite un simple tableau. J'aimerais bien le mettre en forme. Bien que je pourrais utiliser tbl, latexou similaire, cela semble exagéré - le texte brut est vraiment suffisant. Comme c'est simple, je pourrais aussi bien avoir la source comme sortie. Donc, la source devrait aussi bien paraître. Cela semble être un travail parfait pour column -s '|' -t- il trouve les séparateurs et insère automatiquement les espaces à aligner en fonction de la largeur maximale dans chaque colonne. Malheureusement, il supprime les séparateurs, donc je ne peux pas le relancer après une édition supplémentaire. Existe-t-il un bon outil de traitement de texte qui puisse le faire de manière idempotente, afin que sa sortie serve d'entrée? Ou dois-je écrire le mien?

EDIT: voici un exemple de ce que je veux:

foo |   bar | baz
abc def | 12 | 23456

devraient devenir

foo     | bar | baz
abc def | 12  | 3456

Quand ' 'est à la fois le séparateur et l'entretoise, column -tfonctionne bien. Mais mes objets contiennent des espaces, donc je ne peux pas l'utiliser. Le fait que les entretoises soient distinctes des séparateurs complique les choses. Je pense qu'il est utile de les traiter comme des caractères de séparation à côté des séparateurs, mais ce n'est pas le column -s '|' -tcas (bien que le comportement actuel soit également utile).

wnoise
la source
Vous pouvez utiliser le mode organisationnel emacs. La prise en charge des tableaux est en fait assez incroyable, fournissant des fonctionnalités de type feuille de calcul.
vschum
Pas aussi général que ce que je pensais être raisonnable, mais il y a un programme python spécifiquement pour les tables de démarque sur leancrew.com/all-this/2008/08/tables-for-markdown-and-textmate .
wnoise
C'est un problème que je rencontre comme au moins toutes les deux semaines. La seule solution viable pour contourner l' printfholocauste à chaque fois, que j'ai trouvée jusqu'à présent, consiste à ajouter un caractère unique (comme @) dans les données, et à l'utiliser ... | column -s@ -tensuite.
sjas

Réponses:

17

Je ne sais pas si je comprends bien quel est votre problème. Mais, peut-il être résolu en ajoutant un séparateur temporel supplémentaire? vous pouvez donc utiliser le deuxième séparateur pour marquer les séparations, en conservant le séparateur d'origine intact.

Voir cet exemple où j'ajoute un "@" à chacun des "|" donc l'entrée de la commande de colonne serait "xxx @ | yyyy". La colonne traitera le "@" en gardant le "|" intacte:

~$ echo "foo | this is some text | bar" | sed 's/|/@|/g'  | column -s '@' -t
foo   | this is some text   | bar
hmontoliu
la source
Intelligent. Fait presque ce que je veux, et fait en fait ce que j'ai demandé - laisse les séparateurs dedans. Je veux aussi que les espaces à côté des vrais séparateurs puissent être ajustés vers le bas, plutôt que vers le haut, comme ici.
wnoise
@wnoise: utilisez à la sed 's/ *| */@| /g'place
Stéphane Gimenez
@ Stéphane Gimenez: Et en ajoutant sed 's/ |/|/g'après les columncorrectifs les espaces supplémentaires ajoutés. Nous avons maintenant une solution qui fonctionne assez bien pour moi. (Bien que ce serait bien si cela ne dépendait pas d'un personnage supplémentaire comme celui-ci. Et si un n'était pas disponible?)
wnoise
3
@wnoise: Au lieu de @, vous pouvez utiliser quelque chose qui n'apparaît généralement pas dans le texte, comme une faible valeur ASCII, par exemple. $ '\ x01' ... (mais pas $ '\ x00') ...
Peter.O
6

Ce n'était pas disponible lorsque vous avez posé la question mais à partir de la version 2.23 column de util-linuxvous permet de sélectionner le séparateur de sortie via

   -o, --output-separator string
          Specify the columns delimiter for table output (default is two spaces).

Alors lancez simplement:

 column -s '|' -o '|' -t infile
don_crissti
la source
Notez que la util-linuxversion n'est pas disponible sur Ubuntu 18.04 (et probablement d'autres distributions dérivées de Debain) au moment de la rédaction. Seule la bsdmainutilsversion est disponible. lebsdmainutils version ne prend pas en charge le formatage de sortie.
htaccess
5

Voici un script bash. Il n'utilise pas 'column -t`, et le séparateur est géré exactement comme l'IFS, car c'est l'IFS (ou au moins, la version interne d'awk de l'IFS) ... Le délimiteur par défaut est $' \ t '

Ce script remplit complètement le champ le plus à droite.
'colonne' ne fait pas cela.
En remplissant toutes les colonnes, ce script peut être
facilement modifié pour créer également un cadre de tableau.

Remarque. Le fichier d'entrée doit être traité deux fois
(«colonne» devrait également le faire)
La première passe consiste à obtenir les largeurs maximales des colonnes.
La deuxième étape consiste à développer les champs (par colonne)

Ajout de quelques options et correction d'un bug flagrant (renommer les variables :(

  • -l Espace de coupe gauche des champs en retrait
  • -r Ajuster à droite les espaces plus larges que le texte le plus large (pour la colonne)
  • -b Les deux -l et -r
  • -L Le délimiteur de sortie gauche est ajouté
  • -R Le délimiteur de sortie droit est ajouté
  • -B Les deux -L et -R
  • -S Choisissez le séparateur de sortie

#!/bin/bash
#
#   script [-F sep] [file]
#
#   If file is not specified, stdin is read 
#    
# ARGS ######################################################################
l=;r=;L=;R=;O=;F=' ' # defaults
for ((i=1;i<=${#@};i++)) ;do
  case "$1" in
    -- ) shift 1;((i--));break ;;
    -l ) l="-l";shift 1;((i-=1)) ;;        #  left strip whitespace
    -r ) r="-r";shift 1;((i-=1)) ;;        # right strip whitespace
    -b ) l="-l";r="-r";shift 1;((i-=1)) ;; # strip  both -l and -r whitespace
    -L ) L="-L";shift 1;((i-=1)) ;;        #  Left output delimiter is added
    -R ) R="-R";shift 1;((i-=1)) ;;        # Right output delimiter is added
    -B ) L="-L";R="-R";shift 1;((i-=1)) ;; # output Both -L and -R delimiters
    -F ) F="$2";shift 2;((i-=2)) ;; # source separator
    -O ) O="$2";shift 2;((i-=2)) ;; # output  separator. Default = 1st char of -F 
    -* ) echo "ERROR: invalid option: $1" 1>&2; exit 1 ;;
     * ) break ;;
  esac
done
#
if  [[ -z "$1" ]] ;then # no filename, so read stdin
  f="$(mktemp)"
  ifs="$IFS"; IFS=$'\n'; set -f # Disable pathname expansion (globbing)
  while read -r line; do
    printf "%s\n" "$line" >>"$f"
  done
  IFS="$ifs"; set +f # re-enable pathname expansion (globbing)
else
  f="$1"
fi
[[ -f "$f" ]] || { echo "ERROR: Input file NOT found:" ;echo "$f" ;exit 2 ; }
[[ -z "$F" ]] && F=' '        # input Field Separator string
[[ -z "$O" ]] && O="$F"       # output Field Separator
                 O="${O:0:1}" #   use  single char only

# MAIN ######################################################################
max="$( # get max length of each field/column, and output them
  awk -vl="$l" -vr="$r" -vL="$L" -vR="$R" -vF="$F" -vO="$O" '
    BEGIN { if (F!="") FS=F }
    { for (i=1;i<=NF;i++) { 
        if (l=="-l") { sub("^[ \t]*","",$i) }
        if (r=="-r") { sub("[ \t]*$","",$i) }
        len=length($i); if (len>max[i]) { max[i]=len } 
        if (i>imax) { imax=i } 
      } 
    }
    END { for(i=1;i<=imax;i++) { printf("%s ",max[i]) } }
  ' "$f" 
)"

awk -vl="$l" -vr="$r" -vL="$L" -vR="$R" -vF="$F" -vO="$O" -v_max="$max" '
  BEGIN { if (F!="") FS=F; cols=split(_max,max," ") }
  { # Bring each field up to max len and output with delimiter
    printf("%s",L=="-L"?O:"")
    for(i=1;i<=cols;i++) { if (l=="-l") { sub("^[ \t]*","",$i) } 
                           if (r=="-r") { sub("[ \t]*$","",$i) }
      printf("%s%"(max[i]-length($i))"s%s",$i,"",i==cols?"":O) 
    } 
    printf("%s\n",R=="-R"?O:"")
  }
' "$f"

# END #######################################################################    
if  [[ -z "$1" ]] ;then # no filename, so stdin was used
  rm "$f"   # delete temp file
fi
exit
Peter.O
la source
Bien fait. Bien sûr, j'espérais quelque chose qui ne nécessiterait pas d'écrire un nouveau programme.
wnoise
2

Jetez un oeil au plugin vim appelé Tabularize

:Tabularize /<delim>
Amos Folarin
la source
1

Il s'agit d'un ajustement en deux passes sur la réponse de hmontoliu , qui évite d'avoir à coder en dur le délimiteur, en le devinant à partir des données d'entrée.

  1. analyser la saisie pour les caractères non alphanumériques simples entourés d'espaces, les trier par lequel est le plus courant et supposer que le caractère le plus courant est le délimiteur, qui est affecté à $d .
  2. procédez plus ou moins comme dans la réponse de hmonoliu , mais utilisez un NULL ASCII comme remplissage, au lieu d'un @, comme le commentaire de PeterO .

Le code est une fonction qui accepte un nom de fichier, ou bien une entrée de STDIN :

algn() { 
    d="$(grep -ow '[^[:alnum:]]' "${1:-/dev/stdin}"  | \
         sort | uniq -c | sort -rn | sed -n '1s/.*\(.$\)/\1/p')" ;
    sed "s/ *$d */\x01$d /g" "${1:-/dev/stdin}"  | column -s $'\001' -t ;
}

Sortie de algn foo(ou aussi algn < foo):

foo      | bar  | baz
abc def  | 12   | 23456
agc
la source
En regardant cela un an plus tard, il semble que l' invocation STDIN ne puisse pas et ne devrait pas fonctionner car elle utilise deux fois STDIN . Les tests avec des fichiers volumineux (environ 80 millions de lignes) indiquent que cela fonctionne apparemment correctement. Hmm ...
agc
0

Idée utilisée de hmontoliu pour implémenter une commande simple:

#! /bin/bash
delim="${1:-,}"
interm="${2:-\~}"
sed "s/$delim/$interm$delim/g" | column -t -s "$interm" | sed "s/  $delim/$delim/g"

Commentaire:

  • ${1:-,}- est un premier argument avec ,par défaut
  • le premier sedinsère un symbole intermédiaire ( $interm2ème argument ou ~par défaut)
  • columnremplace ensuite le symbole intermédiaire par des espaces qui font l'alignement
  • le second sednettoie les espaces redondants après columncommande

Exemple d'utilisation:

$ echo "
a: bb: cccc
aaaa: b : cc
" | align :

a   : bb: cccc
aaaa: b : cc

Il est également bon dans la mesure où il est idempotent: vous pouvez l'appliquer plusieurs fois et obtenir le même résultat (par exemple lorsque vous éditez dans vim et réalignez).

Alexey
la source