bash: Hur sorterar jag en del av flera rader i bokstavsordning?

Permalänk
Medlem

bash: Hur sorterar jag en del av flera rader i bokstavsordning?

Jag har ett litet kosmetiskt problem som jag gärna skulle vilja få löst. Hur sorterar jag en del av flera rader i bokstavsordning?

Allt mellan början av raden tills två ##. Orden separeras här med comma.

exempel2,exempel4,exempel1,exempel3##inget ska sorteras här efter c,b,a##inget ska sorteras här efter

ska bli

exempel1,exempel2,exempel3,exempel4##inget ska sorteras här efter a,b,c##inget ska sorteras här efter

eller från test= till slutet av raden. Orden separeras här med |.

inget ska sorteras innan här innan,test=b|c|a

ska bli

inget ska sorteras innan här innan,test=a|b|c

Visa signatur
Permalänk
Medlem

Jag har aldrig använt bash tidigare och följande har tagits fram med hjälp av googlande, online-variant av bash samt klipp och klistra. Så antagligen finns det mycket bättre sätt att göra det på. Den löser den första varianten, men bör kunna anpassas för att lösa den andra varianten också.

original="c,b,a##inget ska sorteras här efter" sorted=$(echo ${original%##*}|tr , '\n'|sort|tr '\n' ,|sed '$s/,$/##/')${original#*##} echo $sorted

Ta första delen. Ersätt komma med nyradstecken. Sortera raderna. Ersätt nyradstecken med komma. Byt ut sista kommat med ##. Lägg till andra delen.

Permalänk
Hedersmedlem
Skrivet av johho:

Jag har aldrig använt bash tidigare och följande har tagits fram med hjälp av googlande, online-variant av bash samt klipp och klistra. Så antagligen finns det mycket bättre sätt att göra det på. Den löser den första varianten, men bör kunna anpassas för att lösa den andra varianten också.

original="c,b,a##inget ska sorteras här efter" sorted=$(echo ${original%##*}|tr , '\n'|sort|tr '\n' ,|sed '$s/,$/##/')${original#*##} echo $sorted

Ta första delen. Ersätt komma med nyradstecken. Sortera raderna. Ersätt nyradstecken med komma. Byt ut sista kommat med ##. Lägg till andra delen.

Ser bra ut! Det blir vissa problem med randfall, exempelvis ifall

  1. det finns mer än en förekomst av ## i strängen: c,b,a##inget ska sorteras## här efter

  2. strängen innehåller sekvenser av multipla blanksteg: c   c,b  b,a##inget ska sorteras här efter

  3. om det som ska sorteras innehåller ett element som ser ut som en flagga till echo: c,b,a,-e##inget ska sorteras här efter

Ovanstående saker tror jag i tur och ordning kan adresseras genom att

  1. använda %% för att matcha "girigt" från höger

  2. lägga till citationstecken kring argumenten till echo, eller än hellre gå till printf som generellt beter sig bättre

  3. använda printf med en formatsträng

vilket skulle ge:

original="c,b,a##inget ska sorteras här efter" sorted=$(printf '%s' "${original%%##*}" | tr , '\n' | sort | tr '\n' , | sed '$s/,$/##/')${original#*##} printf '%s\n' "$sorted"


Alternativt är AWK ofta trevligt när det ska hanteras strängar på lite mer komplexa sätt, och det finns färre fällor. Vad gäller hastighet så lönar det sig snabbt att minimera antalet processforkar, så ett enda anrop till awk slår sannolikt att skapa alla ovanstående processer rätt ordentligt, särskilt som indata växer.

Ett AWK-baserat skript (kräver rent tekniskt GNU-varianten gawk pga asort-anropet) som bör lösa uppgiften skulle kunna se ut enligt:

#!/bin/sh awk -F, -vdelim='##' ' { i_delim = index($0, delim); split(substr($0, 1, i_delim - 1), fields); len = asort(fields); for (i = 1; i <= len; ++i) { printf "%s%s", (i > 1) ? FS : "", fields[i] }; print substr($0, i_delim); } ' < "${1:-/dev/stdin}"

Det skriptet agerar då på varje rad i indata, där indata antingen kan komma från en fil som ges som enda argument, eller via stdin, och output skrivs till stdout.

Visa signatur

Nu med kortare användarnamn, men fortfarande bedövande långa inlägg.

Permalänk
Medlem
Skrivet av phz:

... Det blir vissa problem med randfall, ...

Googlingen lärde mig nog mycket för att bli farlig.
Tack för utbildningen.