Wydrukuj wszystkie kolumny oprócz pierwszych trzech


112

Zbyt uciążliwe:

awk '{print " "$4" "$5" "$6" "$7" "$8" "$9" "$10" "$11" "$12" "$13}' things

43
Czy jest jakiś powód, dla którego nie możesz po prostu użyć cut -f3-?
Cascabel

1
@hhh ładne ... Podoba mi się pomysł odpowiedzi podsumowującej.
Chris Seymour

2
@Jefromi - ponieważ są problemy z buforowaniem linii przy wycinaniu, których awk nie ma: stackoverflow.com/questions/14360640/ ...
sdaau


@Jefromi - również cutnie ma wyrażeń regularnych przed {}akcjami, a potem jest o wiele głupiej z ogranicznikami pól (zmienna liczba spacji?) I musisz je określić ręcznie. Myślę, że OP chciał usłyszeć o jakiejś shift Nkomendzie, której nie ma. Najbliższy jest $1="";$2="";(...);print}, ale w moim przypadku pozostawia kilka wiodących spacji (prawdopodobnie separatory).
Tomasz Gandor

Odpowiedzi:


50

Rozwiązanie, które nie dodaje dodatkowych początkowych ani końcowych spacji :

awk '{ for(i=4; i<NF; i++) printf "%s",$i OFS; if(NF) printf "%s",$NF; printf ORS}'

### Example ###
$ echo '1 2 3 4 5 6 7' |
  awk '{for(i=4;i<NF;i++)printf"%s",$i OFS;if(NF)printf"%s",$NF;printf ORS}' |
  tr ' ' '-'
4-5-6-7

Sudo_O proponuje eleganckie ulepszenie przy użyciu operatora trójskładnikowegoNF?ORS:OFS

$ echo '1 2 3 4 5 6 7' |
  awk '{ for(i=4; i<=NF; i++) printf "%s",$i (i==NF?ORS:OFS) }' |
  tr ' ' '-'
4-5-6-7

EdMorton zapewnia rozwiązanie zachowujące oryginalne spacje między polami:

$ echo '1   2 3 4   5    6 7' |
  awk '{ sub(/([^ ]+ +){3}/,"") }1' |
  tr ' ' '-'
4---5----6-7

BinaryZebra zapewnia również dwa niesamowite rozwiązania:
(te rozwiązania zachowują nawet końcowe spacje z oryginalnego ciągu)

$ echo -e ' 1   2\t \t3     4   5   6 7 \t 8\t ' |
  awk -v n=3 '{ for ( i=1; i<=n; i++) { sub("^["FS"]*[^"FS"]+["FS"]+","",$0);} } 1 ' |
  sed 's/ /./g;s/\t/->/g;s/^/"/;s/$/"/'
"4...5...6.7.->.8->."

$ echo -e ' 1   2\t \t3     4   5   6 7 \t 8\t ' |
  awk -v n=3 '{ print gensub("["FS"]*([^"FS"]+["FS"]+){"n"}","",1); }' |
  sed 's/ /./g;s/\t/->/g;s/^/"/;s/$/"/'
"4...5...6.7.->.8->."

Rozwiązanie podane przez larsra w komentarzach jest prawie poprawne:

$ echo '1 2 3 4 5 6 7' | 
  awk '{for (i=3;i<=NF;i++) $(i-2)=$i; NF=NF-2; print $0}' | tr  ' ' '-'
3-4-5-6-7

Oto stała i sparametryzowana wersja rozwiązania larsr :

$ echo '1 2 3 4 5 6 7' | 
  awk '{for(i=n;i<=NF;i++)$(i-(n-1))=$i;NF=NF-(n-1);print $0}' n=4 | tr ' ' '-'
4-5-6-7

Wszystkie inne odpowiedzi przed wrz 2013 są miłe, ale dodaj dodatkowe spacje:


Odpowiedź EdMortona nie zadziałała dla mnie (bash 4.1.2 (1) -release, GNU Awk 3.1.7 lub bash 3.2.25 (1) -release, GNU Awk 3.1.5), ale znalazłam tutaj inny sposób:echo ' This is a test' | awk '{print substr($0, index($0,$3))}'
elysch

1
@elysch nie, to ogólnie nie zadziała, po prostu wydaje się działać przy pewnych określonych wartościach wejściowych. Zobacz komentarz, który dodałem poniżej twojego komentarza pod moją odpowiedzią.
Ed Morton,

1
Cześć @fedorqui. Moja odpowiedź jest pierwsza. W mojej pierwotnej odpowiedzi wyjaśniałem, dlaczego druga odpowiedź nie jest poprawna (dodatkowe początkowe lub końcowe spacje). Niektórzy ludzie zaproponowali ulepszenia w komentarzach. Poprosiliśmy OP, aby wybrał bardziej poprawną odpowiedź, a on / ona wybrał moją. Po tym, jak kilku innych współtwórców zredagowało moją odpowiedź, aby odwołać się do odpowiedzi (zobacz historię). Czy to dla ciebie jasne? Co radzisz mi, aby poprawić zrozumiałość mojej odpowiedzi? Pozdrawiam ;-)
olibre

1
Masz całkowitą rację i bardzo mi przykro z powodu mojego nieporozumienia. Szybko przeczytałem odpowiedź i nie zauważyłem Twojej oryginalnej odpowiedzi (tak, czytałem za szybko). +1 dla samej odpowiedzi, używając fajnej sztuczki, aby zapętlić się do NF-1, a następnie wypisać ostatni element, aby uniknąć dodatkowych białych znaków. I znowu przepraszam! (usuniemy mój komentarz za dzień lub dwa, aby zapobiec nieporozumieniom ze strony przyszłych czytelników).
fedorqui 'SO przestań szkodzić'

1
Użyłbym jakiegoś rodzaju nagłówków: <twoja odpowiedź>, a następnie pozioma reguła, po której następuje duży tytuł „porównanie innych odpowiedzi”. W przeciwnym razie przenieś to porównanie do innej odpowiedzi, ponieważ najwyraźniej ludzie wolą krótkie odpowiedzi w wizji „daj mi mój kod”
:)

75
awk '{for(i=1;i<4;i++) $i="";print}' file

4
To pozostawiłoby początkowe, OFSponieważ nie zajmujesz się NFnp. Wiodącą spacją w rekordach.
Chris Seymour,

70

użyj cięcia

$ cut -f4-13 file

lub jeśli nalegasz na awk i 13 $ jest ostatnim polem

$ awk '{$1=$2=$3="";print}' file

jeszcze

$ awk '{for(i=4;i<=13;i++)printf "%s ",$i;printf "\n"}' file

14
prawdopodobnie lepiej jest użyć „NF” niż „13” w ostatnim przykładzie.
glenn jackman

2
2 scenariusz, o którym decyduje OP. jeśli 13 jest ostatnim polem, użycie NF jest w porządku. Jeśli nie, użycie 13 jest właściwe.
ghostdog74

3
2. musi usunąć 3 kopie OFS od początku $ 0. Trzeci byłby lepszy z printf "%s ",$i, ponieważ nie wiesz, czy $imoże zawierać %slub tym podobne. Ale to spowodowałoby wydrukowanie dodatkowej spacji na końcu.
dubiousjim

38

Spróbuj tego:

awk '{ $1=""; $2=""; $3=""; print $0 }'

1
To miłe ze względu na jego dynamikę. Możesz dodać kolumny na końcu i nie przepisywać swoich skryptów.
MinceMan

1
To pokazuje dokładnie, jaki problem ma rozwiązać pytanie, po prostu zrób coś odwrotnego. A co z wydrukowaniem z setnego pola? Zauważ, że nie masz do czynienia, NFwięc odchodzisz na prowadzenie OFS.
Chris Seymour,

24

Prawidłowym sposobem na to jest zastosowanie interwału RE, ponieważ pozwala on po prostu określić, ile pól ma zostać pominiętych, i zachowuje odstępy między polami dla pozostałych pól.

np. aby pominąć pierwsze 3 pola bez wpływu na odstępy między pozostałymi polami, biorąc pod uwagę format danych wejściowych, który wydaje się omawiać w tym pytaniu, jest po prostu:

$ echo '1   2 3 4   5    6' |
awk '{sub(/([^ ]+ +){3}/,"")}1'
4   5    6

Jeśli chcesz uwzględnić spacje wiodące i niepuste, ale znowu z domyślnym FS, to jest to:

$ echo '  1   2 3 4   5    6' |
awk '{sub(/[[:space:]]*([^[:space:]]+[[:space:]]+){3}/,"")}1'
4   5    6

Jeśli masz FS będący RE, którego nie możesz zanegować w zestawie znaków, możesz najpierw przekonwertować go na pojedynczy znak (RS jest idealne, jeśli jest to pojedynczy znak, ponieważ RS NIE MOŻE pojawić się w polu, w przeciwnym razie rozważ SUBSEP), następnie zastosuj podstawienie interwału RE, a następnie przekonwertuj na OFS. np. jeśli łańcuchy znaków „.” rozdzielają pola:

$ echo '1...2.3.4...5....6' |
awk -F'[.]+' '{gsub(FS,RS);sub("([^"RS"]+["RS"]+){3}","");gsub(RS,OFS)}1'
4 5 6

Oczywiście, jeśli OFS jest pojedynczym znakiem ORAZ nie może pojawić się w polach wejściowych, możesz to zredukować do:

$ echo '1...2.3.4...5....6' |
awk -F'[.]+' '{gsub(FS,OFS); sub("([^"OFS"]+["OFS"]+){3}","")}1'
4 5 6

Wtedy masz ten sam problem, co w przypadku wszystkich rozwiązań opartych na pętli, które ponownie przypisują pola - FS są konwertowane na OFS. Jeśli to jest problem, musisz przyjrzeć się funkcji patsplit () w GNU awks.


Nie działało dla mnie (bash 4.1.2 (1) -release, GNU Awk 3.1.7 lub bash 3.2.25 (1) -release, GNU Awk 3.1.5), ale znalazłem tutaj inny sposób:echo ' This is a test' | awk '{print substr($0, index($0,$3))}'
elysch

2
Nie, to się nie powiedzie, jeśli $ 1 lub $ 2 zawiera ciąg, na który ustawiono $ 3. Spróbuj, na przykład, echo ' That is a test' | awk '{print substr($0, index($0,$3))}'a przekonasz się, aże to, co jest 3 $, odpowiada awewnętrznemu That1 $. W bardzo starej wersji gawk, takiej jak ty, musisz włączyć interwały RE za pomocą flagi --re-interval.
Ed Morton,

2
Masz rację, nie zauważyłem. Przy okazji, naprawdę doceniam twój komentarz. Wiele razy chciałem użyć wyrażenia regularnego z „{}” do określenia liczby elementów i nigdy nie widziałem „--re-interval” w man. +1 dla Ciebie.
elysch

1
1jest spełnionym warunkiem, więc wywołuje domyślną akcję awk wypisywania bieżącego rekordu.
Ed Morton,

1
wiem, jakie to kanoniczne, ale dodałem teraz odpowiedź.
Ed Morton

10

Prawie wszystkie odpowiedzi obecnie dodają spacje wiodące, spacje końcowe lub inny problem z separatorem. Aby wybrać z czwartego pola, w którym separatorem jest biały znak, a separatorem wyjściowym jest pojedyncza spacja, użyj awk:

awk '{for(i=4;i<=NF;i++)printf "%s",$i (i==NF?ORS:OFS)}' file

Aby sparametryzować pole początkowe, możesz zrobić:

awk '{for(i=n;i<=NF;i++)printf "%s",$i (i==NF?ORS:OFS)}' n=4 file

A także pole końcowe:

awk '{for(i=n;i<=m=(m>NF?NF:m);i++)printf "%s",$i (i==m?ORS:OFS)}' n=4 m=10 file

6
awk '{$1=$2=$3="";$0=$0;$1=$1}1'

Wejście

1 2 3 4 5 6 7

Wynik

4 5 6 7

4
echo 1 2 3 4 5| awk '{ for (i=3; i<=NF; i++) print $i }'

3
Lub aby umieścić je w tej samej linii, przypisz 3 $ do 1 $ itd., A następnie zmień NF na odpowiednią liczbę pól. echo 1 2 3 4 5| awk '{ for (i=3; i<=NF; i++) $(i-2)=$i; NF=NF-2; print $0 }'
larsr

Cześć @larsr. Twoja proponowana linia poleceń to jedyna poprawna odpowiedź. Wszystkie inne odpowiedzi dodają dodatkowe spacje (początkowe lub końcowe). Wpisz swoją linię poleceń w nowej odpowiedzi, zagłosuję za nią ;-)
olibre

1
Cześć @sudo_O, rozmawiałem z @larsr na temat wiersza poleceń, które zaproponował w swoim komentarzu. Spędziłem około pięciu minut, zanim zrozumiałem quiproco (nieporozumienie). Zgadzam się, odpowiedź @Vetsin wstawia nowe linie ( ORS) między polami. Brawo za twoją inicjatywę (podoba mi się twoja odpowiedź). Pozdrawiam
olibre

3

Inny sposób uniknięcia używania instrukcji print:

 $ awk '{$1=$2=$3=""}sub("^"FS"*","")' file

W awk, gdy warunek jest prawdziwy, domyślną akcją jest print.


To ma wszystkie problemy, jakie @lhf ma odpowiedź ... jest po prostu krótsza.
Chris Seymour,

Bardzo dobry pomysł;) Lepsze niż moja odpowiedź! (W zeszłym roku zagłosowałem już za twoją odpowiedzią) Pozdrawiam
olibre

Powinno być: awk '{$1=$2=$3=""}sub("^"OFS"+","")' filepodobnie jak OFS, co pozostaje po zmianie zawartości 1 $, 2 $ i 3 $.

3

Nie mogę uwierzyć, że nikt nie zaoferował zwykłej powłoki:

while read -r a b c d; do echo "$d"; done < file

+1 za podobne rozwiązanie ... Ale może to mieć problemy z wydajnością, jeśli filejest duże (> 10-30 KB). W przypadku dużych plików awkrozwiązanie działa lepiej.
TrueY

3

Opcje od 1 do 3 mają problemy z wieloma białymi znakami (ale są proste). Z tego powodu opracowano opcje 4 i 5, które bez problemu przetwarzają wiele białych znaków. Oczywiście, jeśli opcje 4 lub 5 są używane z n=0obiema, zachowa wszelkie wiodące białe spacje, co n=0oznacza brak podziału.

opcja 1

Proste rozwiązanie cięcia (działa z pojedynczymi ogranicznikami):

$ echo '1 2 3 4 5 6 7 8' | cut -d' ' -f4-
4 5 6 7 8

Opcja 2

Wymuszenie ponownego obliczenia awk czasami rozwiązuje problem (działa z niektórymi wersjami awk) dodanych spacji wiodących:

$ echo '1 2 3 4 5 6 7 8' | awk '{ $1=$2=$3="";$0=$0;} NF=NF'
4 5 6 7 8

Opcja 3

Wydrukowanie każdego pola sformatowanego za pomocą printfda większą kontrolę:

$ echo '    1    2  3     4   5   6 7     8  ' |
  awk -v n=3 '{ for (i=n+1; i<=NF; i++){printf("%s%s",$i,i==NF?RS:OFS);} }'
4 5 6 7 8

Jednak wszystkie poprzednie odpowiedzi zmieniają wszystkie FS między polami na OFS. Zbudujmy kilka rozwiązań tego problemu.

Opcja 4

Pętla z sub do usuwania pól i ograniczników jest bardziej przenośna i nie powoduje zmiany FS na OFS:

$ echo '    1    2  3     4   5   6 7     8  ' |
awk -v n=3 '{ for(i=1;i<=n;i++) { sub("^["FS"]*[^"FS"]+["FS"]+","",$0);} } 1 '
4   5   6 7     8

UWAGA: "^ [" FS "] *" ma akceptować dane wejściowe ze spacjami wiodącymi.

Opcja 5

Całkiem możliwe jest zbudowanie rozwiązania, które nie dodaje dodatkowych początkowych ani końcowych białych znaków i zachowuje istniejące białe znaki przy użyciu funkcji gensubz GNU awk, jak to:

$ echo '    1    2  3     4   5   6 7     8  ' |
awk -v n=3 '{ print gensub("["FS"]*([^"FS"]+["FS"]+){"n"}","",1); }'
4   5   6 7     8 

Można go również użyć do zamiany listy pól z liczbą n:

$ echo '    1    2  3     4   5   6 7     8  ' |
  awk -v n=3 '{ a=gensub("["FS"]*([^"FS"]+["FS"]+){"n"}","",1);
                b=gensub("^(.*)("a")","\\1",1);
                print "|"a"|","!"b"!";
               }'
|4   5   6 7     8  | !    1    2  3     !

Oczywiście w takim przypadku OFS jest używany do oddzielenia obu części wiersza, a końcowe białe znaki pól są nadal drukowane.

Uwaga 1: ["FS"]* służy do dopuszczania spacji wiodących w wierszu wejściowym.


Cześć BZ. Twoja odpowiedź jest miła. Ale opcja 3 nie działa na łańcuchu rozpoczynającym się spacją (np " 1 2 3 4 5 6 7 8 ".). Opcja 4 jest fajna, ale zostaw wiodącą spację za pomocą łańcucha rozpoczynającego się spacją. Myślisz, że można to naprawić? Możesz użyć polecenia echo " 1 2 3 4 5 6 7 8 " | your awk script | sed 's/ /./g;s/\t/->/g;s/^/"/;s/$/"/', aby zweryfikować spacje
początkowe

Cześć @olibre. To, że opcja 3 kończy się niepowodzeniem ze spacją, jest powodem rozwinięcia opcji 4 i 5. Opcja 4 pozostawia spację początkową tylko wtedy, gdy na wejściu ją występuje, a n jest ustawione na 0 (n = 0). Uważam, że jest to poprawna odpowiedź, gdy nie ma wyboru pól (nic nie naprawia IMO). Twoje zdrowie.

W porządku. Dzięki za dodatkowe informacje :-) Popraw swoją odpowiedź, podając te dodatkowe informacje :-) Pozdrawiam
olibre

Idealnie :-) Szkoda, że ​​twój użytkownik jest wyłączony :-(
olibre

1

Cut ma flagę --complement, która ułatwia (i przyspiesza) usuwanie kolumn. Wynikowa składnia jest analogiczna do tego, co chcesz zrobić - dzięki czemu rozwiązanie jest łatwiejsze do odczytania / zrozumienia. Uzupełnienie działa również w przypadku, gdy chcesz usunąć nieciągłe kolumny.

$ foo='1 2 3 %s 5 6 7'
$ echo "$foo" | cut --complement -d' ' -f1-3
%s 5 6 7
$

Czy możesz wyjaśnić swoją odpowiedź?
Zulu

Czy powyższa zmiana pomaga w zrozumieniu? Chodzi o to, aby użyć flagi uzupełnienia cięcia. Rozwiązanie powinno być szybszą i bardziej zwięzłą implementacją niż rozwiązania oparte na AWK lub Perlu. Można również wycinać dowolne kolumny.
Michael Back

1

Rozwiązanie Perla, które nie dodaje początkowych ani końcowych spacji:

perl -lane 'splice @F,0,3; print join " ",@F' file

Perl @FTablica autosplit zaczyna się od indeksu, 0podczas gdy pola awk zaczynają się od$1


Rozwiązanie Perla dla danych rozdzielanych przecinkami:

perl -F, -lane 'splice @F,0,3; print join ",",@F' file

Rozwiązanie w Pythonie:

python -c "import sys;[sys.stdout.write(' '.join(line.split()[3:]) + '\n') for line in sys.stdin]" < file


0

Dla mnie najbardziej kompaktowym i zgodnym rozwiązaniem na żądanie jest

$ a='1   2\t \t3     4   5   6 7 \t 8\t '; 
$ echo -e "$a" | awk -v n=3 '{while (i<n) {i++; sub($1 FS"*", "")}; print $0}'

A jeśli masz więcej wierszy do przetworzenia, jak na przykład plik foo.txt , nie zapomnij zresetować i do 0:

$ awk -v n=3 '{i=0; while (i<n) {i++; sub($1 FS"*", "")}; print $0}' foo.txt

Dzięki za forum.


0

Ponieważ byłem zirytowany pierwszą bardzo pozytywną, ale złą odpowiedzią, znalazłem wystarczająco dużo, aby napisać tam odpowiedź, a tutaj złe odpowiedzi są oznaczone jako takie, oto mój kawałek. Nie podobają mi się proponowane rozwiązania, ponieważ nie widzę powodu, aby komplikować odpowiedź.

Mam dziennik, w którym po 5 $ z adresem IP może być więcej tekstu lub brak tekstu. Potrzebuję wszystkiego, od adresu IP do końca linii, jeśli po 5 $ będzie coś. W moim przypadku jest to faktycznie program awk, a nie oneliner awk, więc awk musi rozwiązać problem. Kiedy próbuję usunąć pierwsze 4 pola, używając starej, ładnie wyglądającej i najbardziej pozytywnej, ale całkowicie błędnej odpowiedzi:

echo "  7 27.10.16. Thu 11:57:18 37.244.182.218 one two three" | awk '{$1=$2=$3=$4=""; printf "[%s]\n", $0}'

wypluwa złą i bezużyteczną odpowiedź (dodałem [], aby zademonstrować):

[    37.244.182.218 one two three]

Zamiast tego, jeśli kolumny mają stałą szerokość aż do punktu cięcia i awk, poprawna i dość prosta odpowiedź brzmi:

echo "  7 27.10.16. Thu 11:57:18 37.244.182.218 one two three" | awk '{printf "[%s]\n", substr($0,28)}'

który daje pożądaną wydajność:

[37.244.182.218 one two three]

0

Znalazłem inną możliwość, może przyda się też ...

awk 'BEGIN {OFS=ORS="\t" }; {for(i=1; i<14; i++) print $i " "; print $NF "\n" }' your_file

Uwaga: 1. Dla danych tabelarycznych iz kolumny $ 1 do $ 14


0

Użyj cięcia:

cut -d <The character between characters> -f <number of first column>,<number of last column> <file name>

np .: jeśli masz file1 :car.is.nice.equal.bmw

Uruchom: cut -d . -f1,3 file1 wydrukujecar.is.nice


Wygląda na to, że Twoje rozwiązanie może być wsteczne. Przejrzyj tytuł pytania Wydrukuj wszystkie * oprócz * pierwsze trzy kolumny
Stefan Crain

-1

To nie jest dalekie od niektórych poprzednich odpowiedzi, ale rozwiązuje kilka problemów:

cols.sh:

#!/bin/bash
awk -v s=$1 '{for(i=s; i<=NF;i++) printf "%-5s", $i; print "" }'

Które możesz teraz wywołać z argumentem, który będzie kolumną początkową:

$ echo "1 2 3 4 5 6 7 8 9 10 11 12 13 14" | ./cols.sh 3 
3    4    5    6    7    8    9    10   11   12   13   14

Lub:

$ echo "1 2 3 4 5 6 7 8 9 10 11 12 13 14" | ./cols.sh 7 
7    8    9    10   11   12   13   14

To jest indeksowane 1; jeśli wolisz indeksowane zero, użyj i=s + 1zamiast tego.

Ponadto, jeśli chcesz mieć argumenty za indeksem początkowym i końcowym, zmień plik na:

#!/bin/bash
awk -v s=$1 -v e=$2 '{for(i=s; i<=e;i++) printf "%-5s", $i; print "" }'

Na przykład:

$ echo "1 2 3 4 5 6 7 8 9 10 11 12 13 14" | ./cols.sh 7 9 
7    8    9

W %-5swyrównuje wynik jako 5-znakowego szerokich kolumn; jeśli to nie wystarczy, zwiększ liczbę lub użyj %s(ze spacją), jeśli nie zależy Ci na wyrównaniu.


-1

Rozwiązanie oparte na AWK printf, które pozwala uniknąć% problemu i jest wyjątkowe, ponieważ nie zwraca niczego (bez znaku powrotu), jeśli jest mniej niż 4 kolumny do wydrukowania:

awk 'NF > 3 { for(i=4; i<NF; i++) printf("%s ", $(i)); print $(i) }'

Testowanie:

$ x='1 2 3 %s 4 5 6'
$ echo "$x" | awk 'NF > 3 { for(i=4; i<NF; i++) printf("%s ", $(i)); print $(i) }'
%s 4 5 6
$ x='1 2 3'
$ echo "$x" | awk 'NF > 3 { for(i=4; i<NF; i++) printf("%s ", $(i)); print $(i) }'
$ x='1 2 3 '
$ echo "$x" | awk 'NF > 3 { for(i=4; i<NF; i++) printf("%s ", $(i)); print $(i) }'
$
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.