bash: il modo più breve per ottenere l'ennesima colonna di output


165

Diciamo che durante la tua giornata lavorativa incontri ripetutamente la seguente forma di output in colonne da alcuni comandi in bash (nel mio caso dall'esecuzione svn stnella mia directory di lavoro di Rails):

?       changes.patch
M       app/models/superman.rb
A       app/models/superwoman.rb

per lavorare con l'output del tuo comando - in questo caso i nomi dei file - è necessaria una sorta di analisi in modo che la seconda colonna possa essere utilizzata come input per il comando successivo.

Quello che ho fatto è usare awkper accedere alla seconda colonna, ad esempio quando desidero rimuovere tutti i file (non che si tratti di un tipico caso d'uso :), farei:

svn st | awk '{print $2}' | xargs rm

Dal momento che scrivo molto, una domanda naturale è: esiste un modo più breve (quindi più freddo) per ottenere questo risultato in bash?

NOTA: Quello che sto chiedendo è essenzialmente una domanda di comando shell anche se il mio esempio concreto è sul mio flusso di lavoro svn. Se ritieni che il flusso di lavoro sia stupido e suggerisca un approccio alternativo, probabilmente non ti voterò, ma altri potrebbero, dal momento che la domanda qui è davvero come ottenere l'output del comando n-esima colonna in bash, nel modo più breve possibile . Grazie :)


1
Quando usi spesso un comando, è meglio creare uno script e inserirlo nel tuo percorso. Puoi semplicemente creare una funzione nel tuo bashrc, se preferisci. Non vedo il punto di ridurre l'espressione di selezione delle colonne.
Lynch,

1
Hai ragione, e potrei farlo. Il "punto" è la ricerca di nuovi modi di fare cose a Bash, a scopo di apprendimento ma soprattutto per divertimento :)
Sv1

1
Inoltre, non hai il tuo .bashrc quando stai scrivendo da qualche parte, quindi è utile conoscerlo senza farlo.
Kos,

Risposte:


125

Puoi usare cutper accedere al secondo campo:

cut -f2

Modifica: Siamo spiacenti, non ho capito che SVN non utilizza le schede nel suo output, quindi è un po 'inutile. Puoi personalizzarecut l'output ma è un po 'fragile - qualcosa del generecut -c 10- funzionerebbe, ma il valore esatto dipenderà dalla tua configurazione.

Un'altra opzione è qualcosa di simile: sed 's/.\s\+//'


1
Prova a utilizzare cut -f2con l' svn stoutput. Vedrai che non funziona.
Lynch,

Ho ipotizzato che il reale svn stavrebbe usato le schede nel suo output. Dopo alcuni test sembra che non sia così. Dannazione.
porges,

21
Passare un delimitatore appropriato per -dconsentire a questo di funzionare.
Yogh,

6
Espandere ciò che @Yogh ha detto, per gli spazi come delimitatore, sarebbe simile cut -d" " -f2.
Adamyonk,

W / o awk su stdout usa xargs: svn st | xargs | cut -d "" -f2
vr286

106

Per ottenere lo stesso risultato di:

svn st | awk '{print $2}' | xargs rm

usando solo bash puoi usare:

svn st | while read a b; do rm "$b"; done

Certo, non è più breve, ma è un po 'più efficiente e gestisce correttamente gli spazi bianchi nei nomi dei file.


Che cos'è ae bcome ottenerli?
Timo

1
@Timo arappresenta la prima colonna e brappresenta le colonne rimanenti. Se si desidera stampare la seconda colonna, utilizzare read a b c;e quindi utilizzare echoinvece di rm. Ho usato questo per ottenere una serie di ID che seguivano un modello grep, in modo da poterli interrompere tutti.
Matt Kleinsmith,

36

Mi sono trovato nella stessa situazione e ho finito per aggiungere questi alias al mio .profilefile:

alias c1="awk '{print \$1}'"
alias c2="awk '{print \$2}'"
alias c3="awk '{print \$3}'"
alias c4="awk '{print \$4}'"
alias c5="awk '{print \$5}'"
alias c6="awk '{print \$6}'"
alias c7="awk '{print \$7}'"
alias c8="awk '{print \$8}'"
alias c9="awk '{print \$9}'"

Il che mi permette di scrivere cose come questa:

svn st | c2 | xargs rm

15
Le funzioni Bash sono generalmente più utili. Ho fatto questo: function c() { awk "{print \$$1}" } Quindi puoi fare: svn st | c 2 | xargs rm
Aissen il

8
Ma poi devo digitare uno spazio extra. È troppo faticoso :)
StackedCrooked

16

Prova lo zsh. Supporta l'alias di suffisso, quindi puoi definire X nel tuo .zshrc

alias -g X="| cut -d' ' -f2"

allora puoi fare:

cat file X

Puoi fare un ulteriore passo avanti e definirlo per l'ennesima colonna:

alias -g X2="| cut -d' ' -f2"
alias -g X1="| cut -d' ' -f1"
alias -g X3="| cut -d' ' -f3"

che produrrà l'ennesima colonna del file "file". Puoi farlo anche per un output grep o per un output minore. Questo è molto utile e una caratteristica killer di zsh.

Puoi fare un passo ulteriore e definire D come:

alias -g D="|xargs rm"

Ora puoi digitare:

cat file X1 D

per eliminare tutti i file menzionati nella prima colonna del file "file".

Se conosci la bash, zsh non è un gran cambiamento se non per alcune nuove funzionalità.

HTH Chris


ahh, vedo che hai aggiornato la tua risposta mentre scrivevo il mio commento sopra :) Puoi in qualche modo specificare in modo dinamico quale colonna ottenere o avrei bisogno di n-righe nel mio .zshrc (non che sia importante, solo curioso)
Sv1,

Ho modificato ulteriormente il mio post e definito un suffisso "D" per eliminare i file. Per quanto ne so, dovrai aggiungere una riga per ogni suffisso.
Chris,

Oppure rendilo il primo parametro del comando X. Niente di tutto ciò richiede zsh o alias, tranne forse l'idea peculiare di mettere una pipe in un alias.
Tripleee

1
Non capisco perché a tutti voi piaccia l' cutopzione. Semplicemente non funziona sulla mia macchina usando l'output di svn st. Prova svn st | cut -d' ' -f2solo a vedere cosa succede.
Lynch,

@ Sv1 potresti scrivere un ciclo per definire gli alias, dato che l'alias è solo un comando.
driftcatcher

8

Poiché sembri non avere familiarità con gli script, ecco un esempio.

#!/bin/sh
# usage: svn st | x 2 | xargs rm
col=$1
shift
awk -v col="$col" '{print $col}' "${@--}"

Se lo salvi dentro ~/bin/xe assicurati che ~/binsia nel tuo PATH(ora che è qualcosa che puoi e dovresti inserire nel tuo.bashrc ) hai il comando più breve possibile per estrarre generalmente la colonna n; x n.

Lo script dovrebbe eseguire correttamente il controllo degli errori e la cauzione se invocato con un argomento non numerico o il numero errato di argomenti, ecc .; ma l'espansione su questa versione essenziale di bare-bones sarà nell'unità 102.

Forse vorrai estendere lo script per consentire un delimitatore di colonna diverso. Awk per impostazione predefinita analizza l'input nei campi dello spazio bianco; per usare un delimitatore diverso, usa -F ':'dov'è :il nuovo delimitatore. L'implementazione di questa opzione come opzione per la sceneggiatura la rende leggermente più lunga, quindi la lascio come esercizio per il lettore.


uso

Dato un file file:

1 2 3
4 5 6

Puoi passarlo tramite stdin (usando un inutilecat solo come segnaposto per qualcosa di più utile);

$ cat file | sh script.sh 2
2
5

Oppure forniscilo come argomento per lo script:

$ sh script.sh 2 file
2
5

Qui, sh script.shsi presume che lo script sia salvato come script.shnella directory corrente; se lo salvi con un nome più utile da qualche parte nel tuo PATHe lo contrassegni come eseguibile, come nelle istruzioni sopra, usa invece il nome utile (e no sh).


Bella idea, ma per me non funziona come su sh o bash. Funziona: #! / Bin / bash col = $ 1 maiusc awk "{print \ $$ col}"
mgk

Grazie per questo commento in ritardo. Aggiornato con la tua correzione.
triplo il

1
meglioawk -v col=$col ...
fedorqui "SO smettere di danneggiare" il

@fedorqui Grazie per il tuo feedback, qui e altrove! Aggiornato la risposta. Ora è leggermente più lungo, quindi se vuoi lo script assolutamente più corto che puoi avere, vedi la cronologia delle revisioni per la versione originale.
Tripleee

1
@fedorqui Grazie mille! Aggiunto un piccolo avvertimento catall'esempio per ragioni isteriche (-:
tripleee

5

Sembra che tu abbia già una soluzione. Per semplificare le cose, perché non semplicemente inserire il tuo comando in uno script bash (con un nome breve) ed eseguirlo invece di digitare quel comando 'lungo' ogni volta?


È solo che secondo me non è "bello". Perché? Beh, non voglio dover inondare il mio .bashrc con varie scorciatoie che fanno questo e quello, essenzialmente scrivendo una meta-shell. È abbastanza aliasato così com'è. Detto questo, il tuo suggerimento non è male.
Sv1,

2
.bashrc? Perché dovresti ingombrarlo con cose non legate alla bash. Quello che faccio è scrivere singoli script per ogni "set" di comandi e inserirli tutti in una ~/scriptsdirectory. Quindi aggiungi l'intero ~/scriptsal mio in PATHmodo che io possa semplicemente chiamarli per nome quando ne ho bisogno.
Tarek Fadel,

4
Quindi non inserirlo nel tuo .bashrc. Crea uno script in ~ / bin e assicurati che sia sul tuo PERCORSO.
tripleee,

2

Se stai bene selezionando manualmente la colonna, potresti essere molto veloce usando pick :

svn st | pick | xargs rm

Vai in qualsiasi cella della seconda colonna, premi ce poi premienter


Ho provato lo strumento "pick" a cui hai fatto riferimento e mi piace molto. È ottimo per molte situazioni in cui voglio stampare colonne selezionate ma non voglio digitare "awk '{print $ 3}'" solo per ottenere la colonna desiderata. Sfortunatamente il nome è in conflitto con un diverso strumento "pick" che sembra essere più popolare - può essere installato con "apt install pick". Quindi ho rinominato il tuo strumento in "pickk" sul mio sistema e intendo continuare a usarlo. Grazie per aver pubblicato un riferimento.
William Dye,

1

Si noti che il percorso del file non deve trovarsi nella seconda colonna dell'output svn st. Ad esempio, se si modifica il file e si modifica la sua proprietà, sarà la terza colonna.

Vedi possibili esempi di output in:

svn help st

Esempio di output:

 M     wc/bar.c
A  +   wc/qax.c

Suggerisco di tagliare i primi 8 caratteri di:

svn st | cut -c8- | while read FILE; do echo whatever with "$FILE"; done

Se vuoi essere sicuro al 100% e gestire i nomi di file di fantasia con uno spazio bianco alla fine, ad esempio, devi analizzare l'output XML:

svn st --xml | grep -o 'path=".*"' | sed 's/^path="//; s/"$//'

Ovviamente potresti voler usare un vero parser XML invece di grep / sed.

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.