bash: najkrótsza droga do uzyskania n-tej kolumny wyniku

165

Powiedzmy, że w ciągu dnia wielokrotnie napotykasz następującą formę skolumnizowanego wyniku jakiegoś polecenia w bash (w moim przypadku z wykonania svn stw moim katalogu roboczym Railsów):

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

aby pracować z wyjściem twojego polecenia - w tym przypadku z nazwami plików - wymagany jest jakiś rodzaj analizy, aby druga kolumna mogła być użyta jako dane wejściowe dla następnego polecenia.

To, co robiłem, to awkdostać się do drugiej kolumny, np. Gdy chcę usunąć wszystkie pliki (nie to jest typowy przypadek użycia :), zrobiłbym:

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

Ponieważ często to piszę, naturalne pytanie brzmi: czy istnieje krótszy (a więc fajniejszy) sposób osiągnięcia tego w bashu?

UWAGA: To, o co pytam, jest zasadniczo pytaniem o polecenie powłoki, mimo że mój konkretny przykład dotyczy mojego przepływu pracy svn. Jeśli uważasz, że przepływ pracy jest głupi i zasugeruję alternatywne podejście, prawdopodobnie nie zagłosuję na ciebie, ale inni mogą, ponieważ pytanie tutaj naprawdę brzmi, jak uzyskać wynik polecenia n-tej kolumny w bash, w najkrótszy możliwy sposób . Dzięki :)

Sv1
źródło
1
Kiedy często używasz polecenia, lepiej jest stworzyć skrypt i umieścić go na swojej ścieżce. Jeśli wolisz, możesz po prostu utworzyć funkcję w swoim bashrc. Nie widzę sensu zmniejszania wyrażenia wyboru kolumny.
Lynch
1
Masz rację i mogę to zrobić. Chodzi o poszukiwanie nowych sposobów robienia rzeczy w bashu, w celach edukacyjnych, ale przede wszystkim dla zabawy :)
Sv1
1
Poza tym nie masz swojego .bashrc, kiedy gdzieś ssh-ujesz, więc dobrze jest znać się bez niego.
Kos

Odpowiedzi:

125

cutAby uzyskać dostęp do drugiego pola, możesz użyć :

cut -f2

Edycja: Przepraszamy, nie zdawałem sobie sprawy, że SVN nie używa zakładek w swoich wynikach, więc jest to trochę bezużyteczne. Możesz dostosować cutdane wyjściowe, ale jest to trochę kruche - coś takiego cut -c 10- mogłoby działać, ale dokładna wartość będzie zależeć od twojej konfiguracji.

Inną opcją jest coś takiego: sed 's/.\s\+//'

Porge
źródło
1
Spróbuj użyć cut -f2z svn stwyjściem. Zobaczysz, że to nie działa.
Lynch,
Założyłem, że prawdziwy svn stużyje zakładek w swoim wyjściu. Po kilku testach wydaje się, że tak nie jest. Cholera.
porges
21
Przekazanie odpowiedniego separatora, aby -dto zadziałało.
Yogh
6
Aby rozwinąć to, co powiedział @Yogh, dla spacji jako separatora, wyglądałoby to cut -d" " -f2.
adamyonk
Bez awk na stdout użyj xargs: svn st | xargs | cut -d "" -f2
vr286
106

Aby osiągnąć to samo, co:

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

używając tylko basha, możesz użyć:

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

To prawda, nie jest krótszy, ale jest nieco bardziej wydajny i poprawnie obsługuje białe znaki w nazwach plików.

Rick Sladkey
źródło
Co to jest ai bjak je zdobyć?
Timo
1
@Timo areprezentuje pierwszą kolumnę i bpozostałe kolumny. Jeśli chcesz wydrukować drugą kolumnę, użyj, read a b c;a następnie użyj echozamiast rm. Użyłem tego do uzyskania zestawu identyfikatorów zgodnych ze wzorcem grep, więc mogłem je wszystkie przerwać.
Matt Kleinsmith
36

Znalazłem się w tej samej sytuacji i ostatecznie dodałem te aliasy do mojego .profilepliku:

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}'"

Co pozwala mi pisać takie rzeczy:

svn st | c2 | xargs rm
StackedCrooked
źródło
15
Funkcje Bash są zwykle bardziej przydatne. Zrobiłem to: function c() { awk "{print \$$1}" } Wtedy możesz zrobić: svn st | c 2 | xargs rm
Aissen
8
Ale potem muszę wpisać dodatkową spację. To zbyt męczące :)
StackedCrooked
16

Wypróbuj zsh. Obsługuje alias przyrostków, więc możesz zdefiniować X w swoim .zshrc

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

wtedy możesz:

cat file X

Możesz pójść o krok dalej i zdefiniować ją dla n-tej kolumny:

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

co zwróci n-tą kolumnę pliku "plik". Możesz to zrobić dla wyjścia grep lub mniejszego wyjścia. Jest to bardzo przydatne i zabójcze rozwiązanie zsh.

Możesz pójść o krok dalej i zdefiniować D jako:

alias -g D="|xargs rm"

Teraz możesz wpisać:

cat file X1 D

aby usunąć wszystkie pliki wymienione w pierwszej kolumnie pliku „plik”.

Jeśli znasz bash, zsh nie jest dużą zmianą, z wyjątkiem kilku nowych funkcji.

HTH Chris

Chris
źródło
ahh, widzę, że zaktualizowałeś swoją odpowiedź, kiedy pisałem mój komentarz powyżej :) Czy możesz w jakiś sposób dynamicznie określić, którą kolumnę pobrać, czy potrzebowałbym n-linii w moim .zshrc (nie to, że to ważne, tylko ciekawe)
Sv1
Dokonałem dalszej edycji mojego postu i zdefiniowałem przyrostek „D” do usuwania plików. O ile wiem, będziesz musiał dodać linię dla każdego sufiksu.
Chris,
Lub uczyń go pierwszym parametrem polecenia X. Nic z tego nie wymaga zsh ani aliasów, może z wyjątkiem osobliwego pomysłu umieszczenia potoku w aliasie.
tripleee
1
Nie rozumiem, dlaczego wszystkim podoba się ta cutopcja. Po prostu nie działa na moim komputerze przy użyciu wyjścia svn st. Spróbuj svn st | cut -d' ' -f2tylko zobaczyć, co się stanie.
Lynch,
@ Sv1 możesz napisać pętlę do definiowania aliasów, ponieważ alias to tylko polecenie.
driftcatcher
8

Ponieważ wydaje się, że nie znasz skryptów, oto przykład.

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

Jeśli zapiszesz to ~/bin/xi upewnisz się, że ~/binjest w twoim PATH(teraz to jest coś, co możesz i powinieneś umieścić w swoim .bashrc), masz najkrótszą możliwą komendę do ogólnego wyodrębniania kolumny n; x n.

Skrypt powinien poprawnie sprawdzić błędy i zwolnić, jeśli zostanie wywołany z argumentem nienumerycznym lub z nieprawidłową liczbą argumentów, itp. ale rozszerzenie tej podstawowej wersji podstawowej będzie w jednostce 102.

Może będziesz chciał rozszerzyć skrypt, aby zezwolić na inny ogranicznik kolumn. Awk domyślnie przetwarza dane wejściowe do pól na białych znakach; aby użyć innego separatora, użyj -F ':'gdzie :jest nowym separatorem. Zaimplementowanie tego jako opcji do skryptu sprawia, że ​​jest on nieco dłuższy, więc zostawiam to jako ćwiczenie dla czytelnika.


Stosowanie

Biorąc pod uwagę plik file:

1 2 3
4 5 6

Możesz przekazać go przez stdin (używając bezużytecznegocat jedynie jako symbolu zastępczego dla czegoś bardziej użytecznego);

$ cat file | sh script.sh 2
2
5

Lub podaj go jako argument do skryptu:

$ sh script.sh 2 file
2
5

Tutaj sh script.shzakłada się, że skrypt jest zapisywany script.shw aktualnym katalogu; jeśli zapiszesz go gdzieś w swoim miejscu pod bardziej użyteczną nazwą PATHi oznaczysz jako wykonywalny, tak jak w instrukcjach powyżej, oczywiście użyj zamiast tego użytecznej nazwy (i nie sh).

tripleee
źródło
Niezły pomysł, ale nie działa na mnie tak jak na sh czy bash. To działa: #! / Bin / bash col = $ 1 shift awk "{print \ $$ col}"
mgk
Dzięki za ten późny komentarz. Zaktualizowano wraz z poprawką.
tripleee
1
lepiejawk -v col=$col ...
fedorqui 'SO przestań szkodzić'
@fedorqui Dziękujemy za Twoją opinię, tutaj i gdzie indziej! Zaktualizowałem odpowiedź. Jest teraz nieco dłuższy, więc jeśli chcesz mieć absolutnie najkrótszy skrypt, jaki możesz mieć, zobacz historię wersji oryginalnej wersji.
tripleee
1
@fedorqui Wielkie dzięki! Dodano małe zastrzeżenie do catprzykładu z powodów histerycznych (-:
tripleee
5

Wygląda na to, że masz już rozwiązanie. Aby było to łatwiejsze, dlaczego nie umieścić swojego polecenia w skrypcie basha (z krótką nazwą) i po prostu go uruchomić zamiast wpisywać to „długie” polecenie za każdym razem?

Tarek Fadel
źródło
Chodzi tylko o to, że moim zdaniem nie jest tak „cool”. Czemu? Cóż, nie chcę zalewać mojego .bashrc różnymi skrótami, które robią to i owo, zasadniczo pisząc meta-powłokę. Jest dość aliasowany, jak jest. To powiedziawszy, twoja sugestia nie jest zła.
Sv1,
2
.bashrc? Dlaczego miałbyś zaśmiecać go rzeczami niezwiązanymi z bash? Piszę osobne skrypty dla każdego „zestawu” poleceń i umieszczam je wszystkie w ~/scriptskatalogu. Następnie dodaj całość ~/scriptsdo mojego PATH, abym mógł po prostu zadzwonić do nich po imieniu, kiedy ich potrzebuję.
Tarek Fadel
4
Więc nie umieszczaj go w swoim .bashrc. Utwórz skrypt w ~ / bin i upewnij się, że znajduje się on na Twojej ŚCIEŻCE.
tripleee
2

Jeśli nie przeszkadza Ci ręczne wybieranie kolumny, możesz bardzo szybko skorzystać z funkcji pick :

svn st | pick | xargs rm

Po prostu przejdź do dowolnej komórki w drugiej kolumnie, naciśnij, ca następnie naciśnijenter

Bernardo Rufino
źródło
Wypróbowałem narzędzie „wybierania”, o którym wspominałeś i bardzo mi się podoba. Jest to bardzo dobre w wielu sytuacjach, w których chcę wydrukować wybrane kolumny, ale nie chcę wpisywać „awk '{print $ 3}'” tylko po to, aby uzyskać żądaną kolumnę. Niestety nazwa koliduje z innym narzędziem "pick", które wydaje się być bardziej popularne - można je zainstalować za pomocą "apt install pick". Dlatego zmieniłem nazwę twojego narzędzia na „pickk” w moim systemie i zamierzam nadal go używać. Dziękujemy za przesłanie referencji.
William Dye
1

Zauważ, że ścieżka pliku nie musi znajdować się w drugiej kolumnie wyjścia svn st. Na przykład, jeśli zmodyfikujesz plik i zmodyfikujesz jego właściwość, będzie to trzecia kolumna.

Zobacz możliwe przykłady wyników w:

svn help st

Przykładowe dane wyjściowe:

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

Proponuję wyciąć pierwsze 8 znaków przez:

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

Jeśli chcesz mieć 100% pewności i radzić sobie na przykład z wyszukanymi nazwami plików ze spacjami na końcu, musisz przeanalizować wyjście xml:

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

Oczywiście możesz chcieć użyć prawdziwego parsera XML zamiast grep / sed.

Michał Šrajer
źródło