Comando come `column -t` che mantiene invece i separatori in output


17

Sto modificando una semplice tabella. Mi piacerebbe averlo ben formattato. Anche se potrei usare tbl, latexo simili, questo sembra eccessivo - il testo semplice è davvero sufficiente. Come è semplice, potrei anche avere l'origine come output. Quindi anche la fonte dovrebbe avere un bell'aspetto. Sembra che dovrebbe essere un lavoro perfetto per column -s '|' -t: trova i separatori e inserisce automaticamente gli spazi per allinearli in base alla larghezza massima in ogni colonna. Sfortunatamente, elimina i separatori, quindi non posso eseguirlo nuovamente dopo ulteriori modifiche. Esiste un buon strumento di elaborazione del testo in grado di farlo in modo idempotente, in modo che il suo output serva da input? O devo scrivere il mio?

EDIT: ecco un esempio di ciò che voglio:

foo |   bar | baz
abc def | 12 | 23456

dovrebbe diventare

foo     | bar | baz
abc def | 12  | 3456

Quando ' 'è sia il separatore che il distanziatore, column -tfunzionano bene. Ma i miei oggetti hanno degli spazi, quindi non posso usarli. Avere i distanziatori distinti dai separatori complica le cose. Penso che sia utile che vengano trattati come caratteri di separazione quando accanto ai separatori, ma non è quello che column -s '|' -tfa (anche se ovviamente il comportamento attuale è utile).


Puoi usare emacs org-mode. Il supporto per il tavolo è in realtà piuttosto sorprendente, fornendo funzionalità come fogli di calcolo.
vschum

Non così generale come quello che pensavo sarebbe ragionevole, ma c'è un programma Python specifico per le tabelle di markdown su leancrew.com/all-this/2008/08/tables-for-markdown-and-textmate .
wnoise,

Questo è un problema in cui mi imbatto almeno ogni due settimane. L'unica soluzione praticabile per bypassare printfogni volta l'Olocausto, che ho trovato finora, è l'aggiunta di un carattere univoco (come @) nei dati e da utilizzare in ... | column -s@ -tseguito.
sjas,

Risposte:


17

Non sono sicuro se capisco bene qual è il tuo problema. Ma può essere risolto aggiungendo un ulteriore separatore temporale? quindi puoi usare il secondo separatore per contrassegnare le separazioni, mantenendo intatto il separatore originale.

Vedi questo esempio in cui aggiungo una "@" a ciascuna delle "|" quindi l'input del comando column sarebbe "xxx @ | yyyy". La colonna elaborerà "@" mantenendo "|" intatto:

~$ echo "foo | this is some text | bar" | sed 's/|/@|/g'  | column -s '@' -t
foo   | this is some text   | bar

Intelligente. Fa quasi quello che voglio, e di fatto fa quello che ho chiesto - lascia i separatori dentro. Voglio anche che gli spazi vicino ai veri separatori possano essere regolati verso il basso, piuttosto che verso l'alto, come qui.
wnoise,

@wnoise: utilizzare sed 's/ *| */@| /g'invece
Stéphane Gimenez,

@ Stéphane Gimenez: E aggiungendo sed 's/ |/|/g'dopo le columncorrezioni gli spazi extra aggiunti. Ora abbiamo una soluzione che funziona abbastanza bene per me. (Anche se sarebbe bello se non dipendesse da un personaggio in più come questo. E se uno non fosse disponibile?)
wnoise

3
@wnoise: invece di @, puoi usare qualcosa che in genere non appare nel testo, come un valore ASCII basso, ad es. $ '\ x01' ... (ma non $ '\ x00') ...
Peter.O

6

Questo non era disponibile quando hai posto la domanda, ma a partire dalla v. 2.23 column da util-linuxti consente di selezionare il separatore di uscita tramite

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

Quindi esegui semplicemente:

 column -s '|' -o '|' -t infile

Si noti che la util-linuxversione non è disponibile su Ubuntu 18.04 (e probabilmente altre distro derivate da Debain) al momento della scrittura. È bsdmainutilsdisponibile solo la versione. La bsdmainutilsversione non supporta la formattazione dell'output.
htaccess,

5

Ecco uno script bash. Non usa 'column -t`, e il separatore viene gestito esattamente come l'IFS, perché è l'IFS (o almeno, la versione interna dell'IFS) ... Il delimitatore predefinito è $' \ t '

Questo script completa completamente il campo più a destra.
'colonna' non lo fa.
Compilando tutte le colonne, questo script può essere
facilmente modificato per creare anche un frame di tabella.

Nota. Il file di input deve essere elaborato due volte
(anche 'colonna' dovrebbe fare questo)
Il primo passaggio è ottenere la larghezza massima della colonna.
Il secondo passaggio è espandere i campi (per colonna)

Aggiunte alcune opzioni e risolto un bug evidente (rinominare le variabili :(

  • -l Spazio bianco di ritaglio sinistro di tutti i campi rientrati
  • -r Spazio bianco di ritaglio destro più largo del testo più largo (per la colonna)
  • -b Sia -l che -r
  • -L Viene aggiunto il delimitatore di uscita sinistro
  • -R Viene aggiunto il delimitatore di uscita destro
  • -B Sia -L che -R
  • -S Scegli il separatore di uscita

#!/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

Ben fatto. Certo, speravo in qualcosa che in realtà non avrebbe richiesto la scrittura di un nuovo programma.
wnoise,

2

Dai un'occhiata al plugin vim chiamato Tabularize

:Tabularize /<delim>

1

Questa è una modifica in due passaggi sulla risposta di hmontoliu , che evita la necessità di codificare il delimitatore, indovinandolo dai dati di input.

  1. analizza l'input per singoli caratteri non alfanumerici circondati da spazi, ordinali in base al più comune e supponi che il carattere più comune sia il delimitatore, assegnato a $d .
  2. procedi più o meno come nella risposta di Hmonoliu , ma usa un ASCULL NULL come riempimento, anziché un @, come da commento di PeterO .

Il codice è una funzione che accetta un nome file, oppure input da 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 ;
}

Uscita di algn foo(o anche algn < foo):

foo      | bar  | baz
abc def  | 12   | 23456

Osservandolo un anno dopo, sembra che l' invocazione STDIN non possa e non debba funzionare perché consuma STDIN due volte. Il test con file di grandi dimensioni (circa 80 milioni di righe) indica che sembra funzionare correttamente. Hmm ...
agc,

0

Ho usato l'idea di hmontoliu per implementare un comando semplice:

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

Commento:

  • ${1:-,}- è un primo argomento con ,l'impostazione predefinita
  • il primo sedinserisce un simbolo intermedio ( $intermsecondo argomento o ~di default)
  • quindi columnsostituisce il simbolo intermedio con spazi che eseguono l'allineamento
  • il secondo sedpulisce gli spazi ridondanti dopo il columncomando

Esempio di utilizzo:

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

a   : bb: cccc
aaaa: b : cc

È anche buono in quanto è idempotente: puoi applicarlo più volte e ottenere lo stesso risultato (ad esempio quando modifichi in vim e riallinea).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.