2016-04-21 17 views
7

Supponiamo di avere questi due file:tubo un sacco di file per stdin, estrarre prime colonne, poi si combinano quelle in un nuovo file

$ cat ABC.txt 
ABC DEF 

$ cat PQR.txt 
PQR XTZ 

E vogliamo formare un nuovo file con il 1 ° colonna di ogni file. Ciò può essere ottenuto da:

$ paste -d ' ' <(cut -d ' ' -f 1 ABC.txt) <(cut -d ' ' -f 1 PQR.txt) 
ABC PQR 

Ma io voglio usare questo con tonnellate di file in entrata, non solo abc.txt e PQR.TXT, ma molti di loro. Come possiamo generalizzare questa situazione per passare ogni file nella collezione a tagliato e quindi passare tutte le uscite a incolla (so che questo può essere fatto meglio con awk ma voglio sapere come risolvere questo usando questo approccio).


Modifica 1

Ho scoperto un modo sporco sporca di fare questo:

$ str=''; for i in *.txt; \ 
      do str="${str} <(cut -d ' ' -f 1 ${i})"; \ 
      done ; \ 
    str="paste -d ' ' $str"; \ 
    eval $str 

Ma, per favore, liberare la mia anima con una risposta che non comporta andare a Computer Science Hell.

Edit 2

Ogni file può avere n righe, se questo conta.

+0

hai una sola riga per ogni file? – karakfa

+0

No, ogni file ha n righe. – Dargor

risposta

5

La sostituzione del processo <(somecommand) non esegue il piping su stdin, in realtà apre una pipe su un descrittore di file separato, ad es. 63 e passa in /dev/fd/63. Quando questo "file" è aperto, il kernel * duplica il file fd invece di aprire un file reale.

Possiamo fare qualcosa di simile con l'apertura di una serie di descrittori di file e poi li passa al comando:

# Start subshell so all files are automatically closed 
(
    fds=() 
    n=0 
    # Open a new fd for each process subtitution 
    for file in ./*.txt 
    do 
    exec {fds[n++]}< <(cut -d ' ' -f 1 "$file") 
    done 

    # fds now contain a list of fds like 12 14 
    # prepend "/dev/fd/" to all of them 
    parameters=("${fds[@]/#//dev/fd/}") 

    paste -d ' ' "${parameters[@]}" 
) 

{var}< file è la sintassi di bash per l'assegnazione dinamica descrittore di file. come var=4; exec 4< file; ma senza dover codificare il 4 e lasciare che bash scelga un descrittore di file libero. exec lo apre nella shell corrente.

* Linux, FreeBSD, OpenBSD e XNU/OSX comunque. Questo non è POSIX, ma nessuno dei due è <(..)

+1

Ben fatto; vale la pena ricordare che il metodo '{var}' per definire i descrittori di file richiede Bash 4.1+. – mklement0

+0

Grazie per l'ottima risposta! A proposito, suppongo tu intenda 'var = 4; exec 4 Dargor

1

Dopo uno sguardo più attento, vedo che la risposta di @ quell'altro è fantastica, ma anche qui c'è un altro modo sporco e sporco che è più o meno lo stesso sotto il cofano.

eval "paste -d' ' "$(find *.txt -printf " <(cut -d' ' -f1 '%f')") 
1

dato spazio delimitato da file di input, e ha fornito ':' è un delimitatore di sicurezza, (vale a dire se non ci sono i due punti in ingresso), questo pasta a sed one-liner funziona:

paste -d':' *.txt | sed 's/ [^:]*$//;s/ [^:]*:*/ /g;s/://g' 

(POSIX, senza eval, exec, bashismi, sottoshell, o loo ps.)

+0

@ that-other-guy's e le mie risposte sono ~ 50 volte più veloci di così (testato con 3 file .txt da 10.000.000 righe). – webb

+1

@webb, è bello, ma l'OP non ha detto che stava testando un sacco di piccoli file, piuttosto che alcuni file di grandi dimensioni? Un benchmark per 10.000.000 di file di testo a 3 righe potrebbe essere più rilevante. – agc

+1

punto interessante. la tua risposta è 50 volte più veloce per 2000 file a riga singola, ad es. 40.000 file/secondo o 800 file/secondo per le risposte di @ quell'altro ragazzo e mie! Inoltre, tutte e tre le risposte non riescono completamente per es. 3000 (o più) file. – webb