combina file di testo in base alla colonna


52

Ho due file di testo. Il primo ha contenuto:

Languages
Recursively enumerable
Regular

mentre il secondo ha contenuto:

Minimal automaton
Turing machine
Finite

Voglio combinarli in un file per quanto riguarda le colonne. Quindi ho provato paste 1 2e il suo output è:

Languages   Minimal automaton
Recursively enumerable  Turing machine
Regular Finite

Tuttavia vorrei che le colonne fossero allineate bene come

Languages               Minimal automaton
Recursively enumerable  Turing machine
Regular                 Finite

Mi chiedevo se sarebbe stato possibile farlo senza una gestione manuale?


Inserito il:

Ecco un altro esempio, in cui il metodo Bruce quasi lo inchioda, tranne qualche leggero disallineamento sul quale mi chiedo perché?

$ cat 1
Chomsky hierarchy
Type-0
—

$ cat 2
Grammars
Unrestricted

$ paste 1 2 | pr -t -e20
Chomsky hierarchy   Grammars
Type-0              Unrestricted
—                    (no common name)

3
Quest'ultimo esempio, con disallineamento, è un doozy. Posso duplicarlo su Arch Linux, pr (GNU coreutils) 8.12. Non posso duplicarlo su un vecchio Slackware (11.0) che ho anche in giro: pr (GNU coreutils) 5.97. Il problema è con il carattere '-', ed è in pr, non in incolla.
Bruce Ediger,

1
Ottengo la stessa cosa con EM-DASH con entrambi pre expand... columnsevita questo problema.
Peter

Ho prodotto un output per la maggior parte delle diverse risposte ad eccezione di awk + incolla , che sposterà a sinistra la maggior parte delle colonne se un file di sinistra è più corto di qualsiasi t alla sua destra. Lo stesso, e altro, si applica a "incolla + colonna" che presenta anche questo problema con le righe vuote nelle colonne di sinistra ... Se vuoi vedere tutti gli output insieme. ecco il link: paste.ubuntu.com/643692 Ho usato 4 colonne.
Peter

Ho appena notato qualcosa di fuorviante sul link paste.ubuntu ... Inizialmente ho impostato i dati per testare i miei script (e questo ha portato a fare gli altri) ... quindi i campi che dicono ➀ unicode may render oddly but the column count is ok sicuramente non si applicano a wc-paste-pre wc-paste-prloro mostra le differenze nel conteggio delle colonne. Gli altri sono ok.
Peter

1
@BruceEdiger: il problema dell'allineamento si verifica quando si utilizzano caratteri non ASCII (nella sua domanda, l'OP utilizzava un trattino (-) anziché un carattere meno (-)), molto probabilmente a causa di una gestione errata o assente da prparte del multibyte caratteri nella locale corrente (di solito UTF8).
WhiteWinterWolf

Risposte:


68

Hai solo bisogno del columncomando e digli di usare le schede per separare le colonne

paste file1 file2 | column -s $'\t' -t

Per affrontare la controversia sulla "cella vuota", abbiamo solo bisogno -ndell'opzione per column:

$ paste <(echo foo; echo; echo barbarbar) <(seq 3) | column -s $'\t' -t
foo        1
2
barbarbar  3

$ paste <(echo foo; echo; echo barbarbar) <(seq 3) | column -s $'\t' -tn
foo        1
           2
barbarbar  3

La pagina man della mia colonna indica che -nè "un'estensione Debian GNU / Linux". Il mio sistema Fedora non presenta il problema delle celle vuote: sembra derivato da BSD e la pagina man dice "La versione 2.23 ha cambiato l'opzione -s in modo da non essere avida"


4
glenn: Sei l'eroe dell'ora! Sapevo che c'era qualcosa del genere in giro, ma non riuscivo a ricordarmelo. Sono stato in agguato su questa domanda; attesa per voi :) ... column, naturalmente; che ovvio (col senno di poi) +1 ... Grazie ...
Peter.O

4
Ho appena notato che column -s $'\t' -tignora le celle vuote , risultando in tutte le celle successive a destra (su quella linea) per spostarsi a sinistra; vale a dire, come risultato di una riga vuota in un file, o che sia più breve ... :(
Peter.O

1
@masi, corretto
glenn jackman,

-n non funziona in RHEL. C'è un'alternativa?
Koshur,

Posso finalmente commentare, quindi voglio notare che in precedenza ho aggiunto una risposta che affronta il problema di Peter.O con le esecuzioni di celle vuote utilizzando null.
techno

11

Stai cercando il comodo prcomando dandy :

paste file1 file2 | pr -t -e24

"-E24" è "espandi le tabulazioni a 24 spazi". Fortunatamente, pasteinserisce un carattere di tabulazione tra le colonne, quindi prpuoi espanderlo. Ho scelto 24 contando i caratteri in "Elenco ricorsivo" e aggiungendo 2.


Grazie! Che cosa significa "espandi interruzioni di tabulazione a 24 spazi"?
Tim

Inoltre aggiorno con un esempio in cui il tuo metodo quasi lo inchioda tranne un leggero disallineamento.
Tim

Tradizionalmente i "tabstops" colpiscono ogni 8 spazi. "123TABabc" verrebbe stampato con la larghezza del carattere "a" di 8 caratteri dall'inizio della riga. Impostandolo su 24, la "a" viene posizionata a 24 caratteri di larghezza dall'inizio della riga.
Bruce Ediger,

Tu dici il "-e24" è "espandere tabulazioni a 24 posti" , quindi perché non utilizzare il expandcomando direttamente: paste file1 file2 | expand -t 24?
WhiteWinterWolf

1
@Masi - la mia risposta è simile ma meno complicata della risposta di @ techno di seguito. Non invoca sedquindi c'è un processo che non viene eseguito. Usa quello prche è un comando antico, che risale ai giorni di Unix SysV, penso, quindi potrebbe esistere su più installazioni di expand. È solo vecchia scuola, insomma.
Bruce Ediger,

9

Aggiornamento : qui c'è uno script molto più semplice (quello alla fine della domanda) per l'output tabulato. Basta passargli il nome del file come si farebbe paste... Usa htmlper creare il frame, quindi è modificabile. Conserva più spazi e l'allineamento della colonna viene preservato quando incontra caratteri unicode. Tuttavia, il modo in cui l'editor o il visualizzatore esegue il rendering dell'unicode è un'altra questione del tutto ...

┌──────────────────────┬────────────────┬──────────┬────────────────────────────┐
│ Languages            │ Minimal        │ Chomsky  │ Unrestricted               │
├──────────────────────┼────────────────┼──────────┼────────────────────────────┤
│ Recursive            │ Turing machine │ Finite   │     space indented         │
├──────────────────────┼────────────────┼──────────┼────────────────────────────┤
│ Regular              │ Grammars       │          │ ➀ unicode may render oddly │
├──────────────────────┼────────────────┼──────────┼────────────────────────────┤
│ 1 2  3   4    spaces │                │ Symbol-& │ but the column count is ok │
├──────────────────────┼────────────────┼──────────┼────────────────────────────┤
│                      │                │          │ Context                    │
└──────────────────────┴────────────────┴──────────┴────────────────────────────┘

#!/bin/bash
{ echo -e "<html>\n<table border=1 cellpadding=0 cellspacing=0>"
  paste "$@" |sed -re 's#(.*)#\x09\1\x09#' -e 's#\x09# </pre></td>\n<td><pre> #g' -e 's#^ </pre></td>#<tr>#' -e 's#\n<td><pre> $#\n</tr>#'
  echo -e "</table>\n</html>"
} |w3m -dump -T 'text/html'

---

Una sinossi degli strumenti presentati nelle risposte (finora).
Li ho guardati da vicino; ecco cosa ho trovato:

paste# Questo strumento è comune a tutte le risposte finora presentate # Può gestire più file; quindi più colonne ... Bene! # Delimita ogni colonna con una scheda ... Buono. # Il suo output non è tabulato.

Tutti gli strumenti sottostanti rimuovono questo delimitatore! ... Male se hai bisogno di un delimitatore.

column # Rimuove il delimitatore Tab, quindi l'identificazione del campo è puramente per colonne che sembra gestire abbastanza bene .. Non ho notato nulla di strano ... # Oltre a non avere un delimitatore univoco, funziona benissimo!

expand # Ha solo una singola impostazione della scheda, quindi è imprevedibile oltre 2 colonne # L'allineamento delle colonne non è preciso durante la gestione dell'unicode e rimuove il delimitatore Tab, quindi l'identificazione del campo è puramente allineata

pr# Ha solo una singola scheda, quindi è imprevedibile oltre 2 colonne. # L'allineamento delle colonne non è preciso durante la gestione dell'unicode e rimuove il delimitatore Tab, quindi l'identificazione del campo è puramente mediante l'allineamento delle colonne

Per me, columnè il miglior soluto ovvio come one-liner .. Se vuoi il delimitatore, o una tabluation di arte ASCII dei tuoi file, continua a leggere, altrimenti .. columnsè dannatamente buono :) ...


Ecco uno script che prende qualsiasi numero di file e crea una presentazione tabulata in stile ASCII .. (Ricorda che unicode potrebbe non renderizzare alla larghezza prevista, ad es. ௵ che è un singolo carattere. Questo è abbastanza diverso dalla colonna i numeri sono errati, come nel caso di alcune delle utilità menzionate sopra.) ... L'output dello script, mostrato di seguito, proviene da 4 file di input, denominati F1 F2 F3 F4 ...

+------------------------+-------------------+-------------------+--------------+
| Languages              | Minimal automaton | Chomsky hierarchy | Grammars     |
| Recursively enumerable | Turing machine    | Type-0            | Unrestricted |
| Regular                | Finite            | —                 |              |
| Alphabet               |                   | Symbol            |              |
|                        |                   |                   | Context      |
+------------------------+-------------------+-------------------+--------------+

#!/bin/bash

# Note: The next line is for testing purposes only!
set F1 F2 F3 F4 # Simulate commandline filename args $1 $2 etc...

p=' '                                # The pad character
# Get line and column stats
cc=${#@}; lmax=                      # Count of columns (== input files)
for c in $(seq 1 $cc) ;do            # Filenames from the commandline 
  F[$c]="${!c}"        
  wc=($(wc -l -L <${F[$c]}))         # File length and width of longest line 
  l[$c]=${wc[0]}                     # File length  (per file)
  L[$c]=${wc[1]}                     # Longest line (per file) 
  ((lmax<${l[$c]})) && lmax=${l[$c]} # Length of longest file
done
# Determine line-count deficits  of shorter files
for c in $(seq 1 $cc) ;do  
  ((${l[$c]}<lmax)) && D[$c]=$((lmax-${l[$c]})) || D[$c]=0 
done
# Build '\n' strings to cater for short-file deficits
for c in $(seq 1 $cc) ;do
  for n in $(seq 1 ${D[$c]}) ;do
    N[$c]=${N[$c]}$'\n'
  done
done
# Build the command to suit the number of input files
source=$(mktemp)
>"$source" echo 'paste \'
for c in $(seq 1 $cc) ;do
    ((${L[$c]}==0)) && e="x" || e=":a -e \"s/^.{0,$((${L[$c]}-1))}$/&$p/;ta\""
    >>"$source" echo '<(sed -re '"$e"' <(cat "${F['$c']}"; echo -n "${N['$c']}")) \'
done
# include the ASCII-art Table framework
>>"$source" echo ' | sed  -e "s/.*/| & |/" -e "s/\t/ | /g" \'   # Add vertical frame lines
>>"$source" echo ' | sed -re "1 {h;s/[^|]/-/g;s/\|/+/g;p;g}" \' # Add top and botom frame lines 
>>"$source" echo '        -e "$ {p;s/[^|]/-/g;s/\|/+/g}"'
>>"$source" echo  
# Run the code
source "$source"
rm     "$source"
exit

Ecco la mia risposta originale (ritagliata un po 'al posto della sceneggiatura sopra)

Utilizzo wcper ottenere la larghezza della colonna e sedper il pad destro con un carattere visibile. (solo per questo esempio) ... e quindi pasteper unire le due colonne con un carattere Tab ...

paste <(sed -re :a -e 's/^.{1,'"$(($(wc -L <F1)-1))"'}$/&./;ta' F1) F2

# output (No trailing whitespace)
Languages.............  Minimal automaton
Recursively enumerable  Turing machine
Regular...............  Finite

Se vuoi riempire la colonna di destra:

paste <( sed -re :a -e 's/^.{1,'"$(($(wc -L <F1)-1))"'}$/&./;ta' F1 ) \
      <( sed -re :a -e 's/^.{1,'"$(($(wc -L <F2)-1))"'}$/&./;ta' F2 )  

# output (With trailing whitespace)
Languages.............  Minimal automaton
Recursively enumerable  Turing machine...
Regular...............  Finite...........

Grazie! Hai lavorato molto. È stupefacente.
Tim

5

Ci sei quasi. pastemette un carattere di tabulazione tra ogni colonna, quindi tutto ciò che devi fare è espandere le schede. (Suppongo che i tuoi file non contengano schede.) Devi determinare la larghezza della colonna di sinistra. Con (abbastanza recente) utility GNU, wc -Lmostra la lunghezza della linea più lunga. Su altri sistemi, fai un primo passaggio con awk. La +1quantità di spazio vuoto che desideri tra le colonne.

paste left.txt right.txt | expand -t $(($(wc -L <left.txt) + 1))
paste left.txt right.txt | expand -t $(awk 'n<length {n=length} END {print n+1}')

Se si dispone dell'utilità di colonna BSD, è possibile utilizzarla per determinare la larghezza della colonna ed espandere le schede in una volta sola. ( è un carattere di tabulazione letterale; in bash / ksh / zsh puoi $'\t'invece usare , e in qualsiasi shell puoi usare "$(printf '\t')".)

paste left.txt right.txt | column -s '␉' -t

Nella mia versione di wc, il comando deve essere: wc -L <left.txt... perché, quando un nome di file viene velocizzato come arg da riga di comando , il suo nome viene emesso su stdout
Peter.O

4

Questo è multi-step, quindi non è ottimale, ma qui va.

1) Trova la lunghezza della linea più lunga in file1.txt.

while read line
do
echo ${#line}
done < file1.txt | sort -n | tail -1

Con il tuo esempio, la linea più lunga è 22.

2) Usa awk per riempire file1.txt, riempiendo ciascuna riga di meno di 22 caratteri fino a 22 con l' printfistruzione.

awk 'FS="---" {printf "%-22s\n", $1}' < file1.txt > file1-pad.txt

Nota: per FS, utilizzare una stringa inesistente file1.txt.

3) Usa la pasta come facevi prima.

$ paste file1-pad.txt file2.txt
Languages               Minimal automaton
Recursively enumerable  Turing machine
Regular                 Finite

Se questo è qualcosa che fai spesso, questo può essere facilmente trasformato in uno script.


Nel codice per trovare la riga più lunga, è necessario while IFS= read -r line, altrimenti la shell manipolerà spazi bianchi e barre rovesciate. Ma la shell non è lo strumento migliore per quel lavoro; le versioni recenti di coreutils GNU hanno wc -L(vedi risposta di Fred), oppure è possibile utilizzare awk: awk 'n<length {n=length} END {print +n}'.
Gilles 'SO- smetti di essere malvagio' l'

4

Non sono in grado di commentare la risposta di Glenn Jackman, quindi sto aggiungendo questo per affrontare il problema delle celle vuote che Peter.O ha notato. L'aggiunta di un carattere null prima di ogni scheda elimina le esecuzioni di delimitatori che vengono trattate come un'unica interruzione e risolve il problema. (Inizialmente ho usato gli spazi, ma l'uso del carattere null elimina lo spazio aggiuntivo tra le colonne.)

paste file1 file2 | sed 's/\t/\0\t/g' | column -s $'\t' -t

Se il carattere null causa problemi per vari motivi, provare uno di questi:

paste file1 file2 | sed 's/\t/ \t/g' | column -s $'\t' -t

o

paste file1 file2 | sed $'s/\t/ \t/g' | column -s $'\t' -t

Entrambi sede columnsembrano variare nell'implementazione tra versioni e versioni di Unix / Linux, in particolare BSD (e Mac OS X) rispetto a GNU / Linux.


Quel comando sed sembra non fare nulla. Sostituisco il comando colonna con od -ce non vedo alcun byte null. Questo è su Centos e Ubuntu.
Glenn Jackman,

1
Questo ha funzionato per me in RedHat EL4. Sia sed che colonna sembrano variare nel tempo e nel sistema. In Ubuntu 14.4 l'uso \0non funzionava come nullin sed, ma lo \x0faceva. Tuttavia, la colonna ha restituito un line too longerrore. La cosa più semplice sembra essere usare uno spazio e vivere con il personaggio in più.
techno,

0

Basandosi sulla risposta di bahamat : questo può essere fatto interamente in awk, leggendo i file una sola volta e non creando alcun file temporaneo. Per risolvere il problema come indicato, fare

awk '
        NR==FNR { if (length > max_length) max_length = length
                  max_FNR = FNR
                  save[FNR] = $0
                  next
                }
                { printf "%-*s", max_length+2, save[FNR]
                  print
                }
        END     { if (FNR < max_FNR) {
                        for (i=FNR+1; i <= max_FNR; i++) print save[i]
                  }
                }
    '   file1 file2

Come con molti awkscript di questo genere, il precedente sopra legge file1, salvando tutti i dati savenell'array e calcolando contemporaneamente la lunghezza massima della linea. Quindi legge file2 e stampa i file1dati salvati ( ) fianco a fianco con i file2dati correnti ( ). Infine, se file1è più lungo di file2(ha più righe), stampiamo le ultime righe di file1 (quelle per le quali non vi è alcuna riga corrispondente nella seconda colonna).

Per quanto riguarda il printfformato:

  • "%-nns"stampa una stringa giustificata a sinistra in un campo nnlargo caratteri.
  • "%-*s", nnfa la stessa cosa: gli *dice di prendere la larghezza del campo dal prossimo parametro.
  • Usando per , otteniamo due spazi tra le colonne. Ovviamente può essere regolato.maxlength+2nn+2

Lo script sopra funziona solo per due file. Può essere banalmente modificato per gestire tre file o per gestire quattro file, ecc., Ma sarebbe noioso e rimarrebbe un esercizio. Tuttavia, risulta difficile non modificarlo per gestire un numero qualsiasi di file:

awk '
        FNR==1  { file_num++ }
                { if (length > max_length[file_num]) max_length[file_num] = length
                  max_FNR[file_num] = FNR
                  save[file_num,FNR] = $0
                }
        END     { for (j=1; j<=file_num; j++) {
                        if (max_FNR[j] > global_max_FNR) global_max_FNR = max_FNR[j]
                  }
                  for (i=1; i<=global_max_FNR; i++) {
                        for (j=1; j<file_num; j++) printf "%-*s", max_length[j]+2, save[j,i]
                        print save[file_num,i]
                  }
                }
    '   file*

Questo è molto simile al mio primo copione, tranne

  • Si trasforma max_lengthin un array.
  • Si trasforma max_FNRin un array.
  • Si trasforma savein un array bidimensionale.
  • Legge tutti i file, salvando tutti i contenuti. Quindi scrive tutto l'output dal ENDblocco.

So che questa domanda è vecchia; Mi sono appena imbattuto in esso. Sono d'accordo che pastesia la soluzione migliore; nello specifico, glenn jackman's paste file1 file2 | column -s $'\t' -t. Ma ho pensato che sarebbe stato divertente provare a migliorare l' awkapproccio.
G-Man dice "Reinstate Monica" il
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.