ubuntuusers.de

[AWK] mehrere Felder mit einer %-s Formatierung zusammenfassen

Status: Gelöst | Ubuntu-Version: Ubuntu GNOME 16.04 (Xenial Xerus)
Antworten |

itoss

Avatar von itoss

Anmeldungsdatum:
4. April 2014

Beiträge: 419

Hallo,

ich versuche gerade Strings mit awk zu filtern

Ist es möglich mehrere Felder mit einer %-s Formatierung zusammen zu fassen ?

sprich :

1
echo $STRING |  | awk -F " " '{printf "%-s %4s %-s \n" , $1, $2, $14-$25 }'

sollte dann als Ausgabe eine Formatierungsvorgabe für Feld 2 liefern und ab Feld 2 die Felder 14-25 unformatiert ausgeben.

Das vollständige Auflisten von

1
echo $STRING |  | awk -F " " '{printf "%-s %4s %-s  %-s  %-s  %-s  %-s  %-s  %-s  %-s  %-s \n" , $1, $2, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24,$25 }'

ist doch recht umständlich

Bearbeitet von rklm:

Titel auf Wunsch des Themenstartes geändert

Marc_BlackJack_Rintsch Team-Icon

Ehemalige
Avatar von Marc_BlackJack_Rintsch

Anmeldungsdatum:
16. Juni 2006

Beiträge: 4686

Wohnort: Berlin

@itoss: Tut das was Du willst?

1
2
3
4
5
6
7
8
#!/usr/bin/awk -f
{
    printf("%-s %4s", $1, $2);
    for (i = 14; i <= 25; i++) {
        printf("%s%-s", OFS, $i);
    }
    print "";
}

Testlauf:

$ echo {1..30} | ./test.awk
1    2 14 15 16 17 18 19 20 21 22 23 24 25 

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13205

Geht auch ohne awk:

1
2
$ echo {1..30} | while read -r a b r; do printf '%s %4s %s\n' "$a" "$b" "$r"; done
1    2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

Marc_BlackJack_Rintsch Team-Icon

Ehemalige
Avatar von Marc_BlackJack_Rintsch

Anmeldungsdatum:
16. Juni 2006

Beiträge: 4686

Wohnort: Berlin

@rklm: Geht sicher auch ohne AWK aber so nicht, denn da wird deutlich zu viel ausgegeben.

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13205

Marc_BlackJack_Rintsch schrieb:

@rklm: Geht sicher auch ohne AWK aber so nicht, denn da wird deutlich zu viel ausgegeben.

Wieso? Woher weißt Du denn, dass die Eingabe mehr Felder hat als die Ausgabe haben soll?

Marc_BlackJack_Rintsch Team-Icon

Ehemalige
Avatar von Marc_BlackJack_Rintsch

Anmeldungsdatum:
16. Juni 2006

Beiträge: 4686

Wohnort: Berlin

@rklm: Im ersten Beitrag werden die Felder 1 und 2 und dann die Felder 14 bis 25 ausgegeben. Ich weiss natürlich nicht ob es nach 25 noch welche in der Eingabe gibt, aber die von 3 bis 13 muss es in der Eingabe ja geben und die sollen nicht ausgegeben werden.

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13205

Marc_BlackJack_Rintsch schrieb:

@rklm: Im ersten Beitrag werden die Felder 1 und 2 und dann die Felder 14 bis 25 ausgegeben. Ich weiss natürlich nicht ob es nach 25 noch welche in der Eingabe gibt, aber die von 3 bis 13 muss es in der Eingabe ja geben und die sollen nicht ausgegeben werden.

Ich Blindfisch. Danke für die Brille!

itoss

(Themenstarter)
Avatar von itoss

Anmeldungsdatum:
4. April 2014

Beiträge: 419

viele Wege führen nach Rom - ich suche nach der "Autobahn ohne Schlaglöcher" 😉

Die Beispiele mit den Schleifen funktionieren zwar, machen den Code aber unleserlicher, komplizierter, und langsamer. Von der Syntax her würde ich gern bei AWK bleiben.

gibt es keine AWK interne Funktion, ähnlich wie :

1
echo "S T R I N G" | cut -d " " -f1,3-5
1
echo "S T R I N G" | awk -F " " '{printf "%-s %-s %5-s %-s \n",$1, $3, $4, $5 }'

Bei 30 Feldern und mehr werden die Zeilen extrem lang und unübersichtlich, abgesehen davon ist eine interne AWK Stringverarbeitung wesentlich schneller als eine Schleife.

AWK hat Cut gegenüber den Vorteil in einem Durchlauf mehrere Feldseparatoren zu definieren ⇒ echo string | awk -F "[: ]" # für : und Leerzeichen als Feldseparator - wüsste nicht wie das mit cut geht ?!

kB Team-Icon

Supporter, Wikiteam
Avatar von kB

Anmeldungsdatum:
4. Oktober 2007

Beiträge: 9704

Wohnort: Münster

itoss schrieb:

[…] awk […] Ist es möglich mehrere Felder mit einer %-s Formatierung zusammen zu fassen ?

  • Nein. Das Format "%-s" fasst bei printf nichts zusammen, sondern gibt ein Argument als linksbündigen String aus.

  • awk und printf kennen das Konzept „Bereich von Variablen“ nicht.

[…] Das vollständige Auflisten von

1
echo $STRING |  | awk -F " " '{printf "%-s %4s %-s  %-s  %-s  %-s  %-s  %-s  %-s  %-s  %-s \n" , $1, $2, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24,$25 }'

ist doch recht umständlich

  • Für wiederkehrende Aufgaben haben die Programmierer schon vor langer Zeit das Konzept „Schleife“ erfunden. Sie führt auch hier zum Ziel, wie Dir bereits gezeigt wurde:

    echo $STRING | awk -F " " '{printf $1; printf $2; for(i=14; i<=25; i++) printf $i }'

Marc_BlackJack_Rintsch Team-Icon

Ehemalige
Avatar von Marc_BlackJack_Rintsch

Anmeldungsdatum:
16. Juni 2006

Beiträge: 4686

Wohnort: Berlin

@kB: Bei printf sollte man niemals den Formatstring weg lassen, sonst bekommt man Probleme wenn in dem Wert den man versucht auszugeben etwas enthalten ist was einem gültigen Formatstring entspricht:

$ echo 'foo'    | awk '{printf $1; print ""}'
foo
$ echo 't%s==0' | awk '{printf $1; print ""}'
awk: (FILENAME=- FNR=1) fatal: not enough arguments to satisfy format string
        `t%s==0'
          ^ ran out for this one 

Also eher so:

1
echo $STRING | awk -F " " '{print $1, $2; for(i=14; i<=25; i++) printf "%s%s", OFS, $i; print ""}'

itoss

(Themenstarter)
Avatar von itoss

Anmeldungsdatum:
4. April 2014

Beiträge: 419

Das Schleifen auch innerhalb des printf Befehls funktionieren ist zwar ne tolle Sache aber der Gesamtstring ist jetzt noch länger 🤓

ohne schleife und unformatierte Ausgabe

1
echo "$STRING" | awk  '{print $1, $4, $5, $6, $7, $8, $9, $2, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20,$21, $22, $23, $24}'

jetzt könnte man den o.g. Ausgabestring mit u.g. awk Bearbeitung für alle Felder gleich formatieren

1
echo "$OBRIGER_AUSGABESTRING" | awk  '{for(i=1; i<=25; i++) printf "%s%s", OFS, $i; print ""}'

um auf das gleiche Ergebnis wie in Bsp. 1 zu kommen ohne Zeilenumbruch :

1
echo "$STRING" | awk  '{printf "%s %s %s %s %s %s %s %s", $1, $4, $5, $6, $7, $8, $9, $2; for(i=11; i<=25; i++) printf "%s%s", OFS, $i; print ""}'

nehme ich für den ersten teil nur print

1
echo "$STRING" | awk  '{print $1, $4, $5, $6, $7, $8, $9, $2; for(i=11; i<=25; i++) printf "%s%s", OFS, $i; print ""}'

habe ich ein Zeilenumbruch nach der $2 Ausgabe.

track

Avatar von track

Anmeldungsdatum:
26. Juni 2008

Beiträge: 7174

Wohnort: Wolfen (S-A)

itoss schrieb:

1
echo "$STRING" | awk  '{print $1, $4, $5, $6, $7, $8, $9, $2, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20,$21, $22, $23, $24}'

Jetzt hast Du Dein Beispiel aber noch "mal eben" ganz gehörig erweitert ...

Aber ok - Du wolltest einen Bereichs-"Operator". Den gibt es bei awk nicht, aber bei der Shell:

track@track:~$ echo x{1..25}  |  bash -c 'read -a x; for i in 0 {3..8} 1 {13..24}; do printf "%s " ${x[i]}; done; echo'
x1 x4 x5 x6 x7 x8 x9 x2 x14 x15 x16 x17 x18 x19 x20 x21 x22 x23 x24 x25 

(Ja, alle Indizes werden von 0 statt von 1 an gezählt, aber das ist bei Shell-Arrays so.)
Bei awk kannst Du die Zahlen mit split direkt in ein Array übergeben:

track@track:~$ echo x{1..25}  |  awk '{split("1 4 5 6 7 8 9 2 14 15 16 17 18 19 20 21 22 23 24 25",a); for(i=1;a[i];i++) printf "%s ",$a[i]; print ""}'
x1 x4 x5 x6 x7 x8 x9 x2 x14 x15 x16 x17 x18 x19 x20 x21 x22 x23 x24 x25  

... oder Du regelst die Sprünge in der Liste mit entsprechenden Bedingungen:

track@track:~$ echo x{1..25}  |  awk '{for(i=1;i<=25;i++) {if(i==2)i=4; if(i==10)i=2; if(i==3)i=14; printf "%s ",$i}; print ""}'
x1 x4 x5 x6 x7 x8 x9 x2 x14 x15 x16 x17 x18 x19 x20 x21 x22 x23 x24 x25  

Das ist zwar alles nicht so elegant, wie Du es Dir vorgestellt hast, aber andere Varianten wüsste ich jetzt auch nicht.

LG,

track

kB Team-Icon

Supporter, Wikiteam
Avatar von kB

Anmeldungsdatum:
4. Oktober 2007

Beiträge: 9704

Wohnort: Münster

Was ist eigendlich Dein Ziel?

  • Willst Du den Befehl cut in awk nachprogrammieren? Ja, das geht, erfordert aber einige Zeilen, wenn man es übersichtlich darstellen und vollständig machen möchte.

  • Oder willst Du einfach die Reihenfolge der Felder ändern und einige auslassen? Das macht cut perfekt. Nutze es.

  • Oder was?

itoss

(Themenstarter)
Avatar von itoss

Anmeldungsdatum:
4. April 2014

Beiträge: 419

Gut - eine einfache Einzeilerlösung ohne Schleife gib es wohl nicht.

am schnellsten ist die Variante :

1
echo "$STRING" | awk  -F "[ab]" '{print $1, $4, $5, $6, $7, $8, $9, $2, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20,$21, $22, $23, $24}'

track, wirklich kürzer ist die Schreibweise auch nicht (unter 25 Feldern ) 🙄 - aber trotzdem danke für die Bespiele 😉

kb, im Prinzip suchte ich eine cut Lösung mit mehrfachen Feldtrenner. Cut interpretiert allerdings jedes Leerzeichen als Feld, sind es mehrere Leerzeichen nach einander werden diese von awk als ein Leerzeichen gesehen, und die Felder dementsprechend gezählt, bei cut irgentwie nicht.

kB Team-Icon

Supporter, Wikiteam
Avatar von kB

Anmeldungsdatum:
4. Oktober 2007

Beiträge: 9704

Wohnort: Münster

itoss schrieb:

[…] im Prinzip suchte ich eine cut Lösung mit mehrfachen Feldtrenner. Cut interpretiert allerdings jedes Leerzeichen als Feld, sind es mehrere Leerzeichen nach einander werden diese von awk als ein Leerzeichen gesehen, und die Felder dementsprechend gezählt, bei cut irgentwie nicht.

  • cut trennt die Felder bei einem bestimmten Zeichen. Dieses ist per Vorgabe der Tabulator, kann aber mit der Option -d auf jedes andere einzelne Zeichen gesetzt werden.

  • awk trennt die Felder an Stellen, die einem regulären Ausdruck entsprechen. Dieses ist per Vorgabe white space, kann aber mit der Option -F bzw. per Variable FS auf einen beliebigen regulären Ausdruck gesetzt werden.

  • Wenn Du Zeichenfolgen als Trenner verwenden willst, geht das also nicht mit cut, aber natürlich per awk.

  • Eine andere Möglichkeit wäre, vorher jedes Vorkommen von white space zu normalisieren, z.B. auf einen einzelnen Tabulator und dann für das Umsortieren der Felder cut zu verwenden. Dann geht natürlich auch die von Dir so geschätzte Bereichsauswahl. Die Normalisierung von white space gelingt z.B. mit sed:

    sed -r 's/\s+/\t/' EINGABEDATEI | cut -f1,4,5-9,2-11-

Antworten |