Textamynstur og gagnahreinsun
Sum Misc-verkefni afhenda okkur ekki fallega mynd eða dularfullt hljóð. Þau gefa okkur 50.000 línur af texta og eina spurningu:
Hvaða auðkenni birtist oftast rétt áður en orðið
OPENkom fyrir?
Við gætum lesið línurnar handvirkt. Betra er að smíða lítið færiband:
lesa → sía → draga út → samræma → raða → telja → staðfesta
Í þessum kafla notum við grep, sort, uniq, cut, awk, jq og Python til að breyta hávaða í svar. Öll dæmi nota tilbúin CTF-gögn.
Hreinsun er túlkun
Þegar við fjarlægjum línur, breytum hástöfum eða köstum ógildum færslum erum við að taka ákvörðun. Við varðveitum upprunalega skrá, skráum reglurnar og teljum hversu mörg gögn hurfu.
Byrjum á spurningu og sýnishorni
Segjum að atburdir.txt innihaldi:
2026-07-10T10:00:01Z INFO user=anna action=OPEN item=blue
2026-07-10T10:00:02Z DEBUG cache=hit
2026-07-10T10:00:03Z INFO user=bjarni action=CLOSE item=red
BROKEN LINE
2026-07-10T10:00:04Z INFO user=anna action=OPEN item=green
2026-07-10T10:00:05Z INFO user=katla action=OPEN item=blue
Áður en við skrifum flókna skipun skoðum við:
wc -l atburdir.txt
head -n 5 atburdir.txt
tail -n 5 atburdir.txt
file atburdir.txt
Við spyrjum:
- Er ein færsla í hverri línu?
- Eru hauslínur eða auðar línur?
- Er afmarkari alltaf sá sami?
- Eru öll gildi á sama sniði?
- Hvaða lína brýtur mynstrið?
- Þurfum við allar línur til að svara spurningunni?
Skoðaðu lítið sýni, en staðfestu á öllu safninu
Fyrstu tíu línurnar geta litið reglulega út þótt lína 20.000 sé öðruvísi. Notaðu sýnið til að smíða aðferðina og keyrðu síðan talningar og villupróf á heildinni.
Pípur sem lítil færibönd
Við höfum áður lært um pípur og staðalstrauma. Píputáknið | sendir úttak einnar skipunar í næstu:
grep 'action=OPEN' atburdir.txt | head
Lengra færiband:
grep 'action=OPEN' atburdir.txt \
| grep -oE 'user=[^ ]+' \
| cut -d= -f2 \
| sort \
| uniq -c \
| sort -nr
Úttak fyrir sýnidæmið:
2 anna
1 katla
Skrefin eru:
- halda aðeins
OPEN-línum, - draga út
user=og gildið fram að næsta bili, - fjarlægja
user=, - raða notandanöfnum,
- telja samliggjandi eins línur,
- raða talningunni stærstu fyrst.
Við getum prófað hvert skref eitt og sér. Það er auðveldara að finna villu í sex litlum skrefum en einni óútskýrðri galdraskipun.
grep: finnum línur og textabrot
Einföld leit:
grep 'OPEN' atburdir.txt
Óháð há-/lágstöfum:
grep -i 'open' atburdir.txt
Með línunúmerum:
grep -n 'BROKEN' atburdir.txt
Andhverf leit, það er línur sem passa ekki:
grep -vE '^2026-[0-9]{2}-[0-9]{2}T' atburdir.txt
-E virkjar útvíkkaðar reglulegar segðir (e. extended regular expressions). -o prentar aðeins textabrotið sem passaði:
grep -oE 'gg\{[^}]+\}' bland.txt
Mynstrið merkir:
gg\{ bókstaflegi textinn gg{
[^}]+ einn eða fleiri stafir sem eru ekki }
\} bókstaflegur }
Gráðug mynstur
Mynstrið:
gg\{.*\}
getur tekið of mikið úr línunni:
gg{fyrri} rusl gg{seinni}
.* er gráðugt og getur passað frá fyrsta { til síðasta }. [^}]+ stöðvar við fyrsta lokaslaufusviga og er skýrara fyrir einfalt fánasnið.
Æfing: dragðu út gilda frambjóðendur
Æfing 1
bland.txt inniheldur:
ekkert hér
svar=gg{fyrsti}
bilað gg{vantar_lok
tveir: gg{annar} og gg{thridi}
tomt=gg{}
Hvað skilar eftirfarandi skipun?
grep -oE 'gg\{[^}]+\}' bland.txt
Lausn við æfingu 1
Úttakið er:
gg{fyrsti}
gg{annar}
gg{thridi}
gg{vantar_lok hefur engan } og passar ekki. gg{} hefur engan staf milli sviganna, en + krefst eins eða fleiri. grep -o getur skilað fleiri en einu samsvörunarbrotum úr sömu línu.
sort og uniq: röðun, hópun og talning
uniq sameinar aðeins samliggjandi eins línur. Því röðum við venjulega fyrst:
sort notendur.txt | uniq -c
Algengt tíðnifæriband:
sort notendur.txt | uniq -c | sort -nr
uniq -csetur talningu fremst,sort -nraðar tölulega,sort -rsnýr röðinni við.
Án -n er textaröðun:
10
2
9
Með sort -n fæst töluleg röð:
2
9
10
Staðfærsla og endurtekning
Röðun getur ráðist af tungumála- og staðfærslustillingum. Þegar CTF-lausn þarf nákvæma bætaröð getum við notað:
LC_ALL=C sort gildi.txt
Skráðu stillinguna í lausninni. C-röðun er ekki „rétt íslensk röðun“, heldur fyrirsjáanleg röðun eftir bætagildum fyrir mörg einföld ASCII-verkefni.
Æfing: af hverju þarf sort?
Æfing 2
Skráin er:
anna
katla
anna
anna
katla
Hver er munurinn á:
uniq -c notendur.txt
og:
sort notendur.txt | uniq -c
Lausn við æfingu 2
Fyrri skipunin telur aðeins samliggjandi runur:
1 anna
1 katla
2 anna
1 katla
Seinni skipunin setur öll eins nöfn saman:
3 anna
2 katla
Ef upprunaleg röð hefur merkingu varðveitum við hana í frumskránni. Röðunin er aðeins afleitt skref fyrir tíðnitalningu.
cut og afmarkarar
Afmarkari (e. delimiter) skilur reiti að. Fyrir einfaldan tvípunktaskiptan texta:
anna:OPEN:blue
katla:CLOSE:red
getum við valið annan reit:
cut -d: -f2 gogn.txt
OPEN
CLOSE
cut er fljótt og skýrt þegar:
- afmarkarinn er eitt tákn,
- allir reitir fylgja sama einfalda sniði,
- afmarkarinn getur ekki komið fyrir inni í gildi.
Það skilur ekki sjálfkrafa gæsalappareglur CSV.
awk: dálkar og einfaldir útreikningar
Fyrir bilaskipta dálka:
anna OPEN 12
katla CLOSE 7
anna OPEN 5
getum við lagt saman þriðja dálk fyrir OPEN:
awk '$2 == "OPEN" { summa += $3 } END { print summa }' gogn.txt
Úttak:
17
awk notar sjálfgefið runur bila og dálkmerkja sem skil. Með -F veljum við annan afmarkara:
awk -F: '$2 == "OPEN" { print $1 }' gogn.txt
Fyrir flókið CSV, JSON eða óregluleg gögn veljum við hins vegar sniðsértækt tól.
CSV: kommur geta verið inni í reit
CSV (e. comma-separated values) er textasnið fyrir töflugögn. CSV-skrá getur litið svona út:
name,place,score
anna,"Reykjavík, Ísland",10
katla,Akureyri,12
Skipunin:
cut -d, -f2 tafla.csv
les fyrri gagnalínuna rangt sem "Reykjavík, því cut skilur ekki að komman inni í gæsalöppum tilheyri reitnum.
Python hefur CSV-lesara:
import csv
with open("tafla.csv", newline="", encoding="utf-8") as f:
for lina in csv.DictReader(f):
print(lina["name"], lina["place"], int(lina["score"]))
Úttak:
anna Reykjavík, Ísland 10
katla Akureyri 12
Ekki skrifa þinn eigin CSV-skipti með split(',')
CSV getur haft gæsalappir, kommur og línuskipti inni í reitum. Notaðu csv-safnið nema verkefnið skilgreini einfaldara sérsnið.
Æfing: hvaða tól?
Æfing 3
Veldu hentugt tól fyrir hvert inntak:
anna:OPEN:blue, þar sem tvípunktur má aldrei vera inni í gildi.- Staðlað CSV með gæsalöppum og kommum inni í reitum.
- JSON-færsla í hverri línu.
- Þrír bilaskiptir töludálkar sem á að leggja saman.
Lausn við æfingu 3
cut -d:, eðaawk -F:, hentar einfalda sniðinu.- Python
csvhentar CSV-reglunum. jqhentar JSON Lines.awkhentar einföldum dálkaútreikningi.
Fleiri en eitt tól getur leyst sum atriði, en við veljum það sem skilur uppbyggingu sniðsins í stað þess að reiða sig á heppni.
JSON og jq
JSON (e. JavaScript Object Notation) geymir skipulögð gögn sem hluti, fylki og frumgildi. JSON Lines (JSONL) geymir eina JSON-einingu í hverri línu. JSON Lines-skrá hefur eina JSON-einingu í hverri línu:
{"user":"anna","action":"OPEN","score":10}
{"user":"katla","action":"CLOSE","score":7}
{"user":"anna","action":"OPEN","score":5}
Veljum OPEN og prentum notandann sem hráan texta:
jq -r 'select(.action == "OPEN") | .user' atburdir.jsonl
anna
anna
Leggjum saman stig:
jq -s 'map(select(.action == "OPEN") | .score) | add' atburdir.jsonl
15
-s (e. slurp) les allar línurnar í eitt JSON-fylki. Það er þægilegt fyrir lítil CTF-gögn en getur notað mikið minni á mjög stórri skrá.
Óvissir eða vantaðir reitir
jq -r 'select(.action? == "OPEN") | .user // "<vantar>"' atburdir.jsonl
.action?forðast villu ef uppbygging er óvænt,//velur varagildi þegar vinstri niðurstaðan ernulleða vantar.
Við teljum samt ógildar línur. jq hættir með villu á brotnu JSON og sú villa má ekki hverfa óséð í löngu færibandi.
Æfing: síaðu JSON
Æfing 4
Skráin dyr.jsonl inniheldur:
{"door":"A","open":true,"user":"anna"}
{"door":"B","open":false,"user":"katla"}
{"door":"A","open":true,"user":"bjarni"}
{"door":"C","open":true,"user":"anna"}
Skrifaðu jq-skipun sem skilar notendum sem opnuðu dyr A. Hvert er úttakið?
Lausn við æfingu 4
Skipunin er:
jq -r 'select(.door == "A" and .open == true) | .user' dyr.jsonl
Úttak:
anna
bjarni
Ef við viljum einstök nöfn getum við bætt við | sort -u, en þá breytum við röð og fjarlægjum endurtekningar. Það gerum við aðeins ef spurningin biður um það.
Reglulegar segðir í Python
Python hentar þegar við þurfum að:
- tengja mörg útdregin gildi,
- staðfesta heila línu,
- telja frávik,
- vinna með Unicode,
- skrifa skýrar prófanir.
import re
mynstur = re.compile(r"^user=([a-z]+) action=(OPEN|CLOSE) score=([0-9]+)$")
for nr, lina in enumerate(open("gogn.txt", encoding="utf-8"), start=1):
lina = lina.rstrip("\n")
samsvorun = mynstur.fullmatch(lina)
if not samsvorun:
print(f"Ógild lína {nr}: {lina!r}")
continue
notandi, adgerd, stig = samsvorun.groups()
print(notandi, adgerd, int(stig))
fullmatch() krefst þess að öll línan passi. Það kemur í veg fyrir að við samþykkjum óvænt rusl fyrir eða eftir gildin.
Nafngreindir hópar skýra mynstrið
Fyrir flóknara mynstur getum við notað (?P<user>...) og síðan samsvorun.group("user"). Skýr heiti eru oft verðmætari en örfáir sparaðir stafir.
Talning með Counter
from collections import Counter
notendur = ["anna", "katla", "anna", "bjarni", "anna"]
talning = Counter(notendur)
for nafn, fjoldi in talning.most_common():
print(fjoldi, nafn)
3 anna
1 katla
1 bjarni
Ef tvö gildi hafa sömu tíðni þurfum við að vita hvernig jafntefli á að leysa. most_common() varðveitir röð fyrstu birtingar fyrir jafna talningu í nútíma Python, en verkefnið gæti krafist stafrófsröðunar. Skilgreinum regluna sjálf:
for nafn, fjoldi in sorted(talning.items(), key=lambda x: (-x[1], x[0])):
print(fjoldi, nafn)
Gagnahreinsun með mælingum
Gagnahreinsun (e. data cleaning) er að finna og meðhöndla ófullkomin, ósamræmd eða óæskileg gögn samkvæmt skýrum reglum.
Gott CTF-forrit telur:
heild = 0
gildar = 0
ogildar = 0
with open("gogn.txt", encoding="utf-8") as f:
for lina in f:
heild += 1
lina = lina.strip()
if not lina or lina.startswith("#"):
continue
if "=" not in lina:
ogildar += 1
continue
gildar += 1
print({"heild": heild, "gildar": gildar, "ogildar": ogildar})
Við greinum líka á milli:
- auðrar línu sem má hunsa,
- athugasemdar sem sniðið leyfir,
- ógildrar færslu,
- gildrar færslu sem uppfyllir ekki síuskilyrði.
Annars getur „100 færslur fundust“ í raun þýtt „100 af 150; 50 hurfu vegna villu“.
Skeljarvillur sem breyta niðurstöðu
Setjum mynstur í gæsalappir
grep -E 'gg\{[^}]+\}' skra.txt
Án gæsalappa getur skelin túlkað *, ?, hornklofa og önnur tákn áður en grep sér þau.
Skráarheiti sem byrjar á bandstriki
grep 'mynstur' -- -undarlegt-nafn.txt
-- segir að valkostum sé lokið.
Villur í pípu
Í Bash getur síðasta skipunin tekist þótt fyrri skipun mistakist. Fyrir lausnarskrift er gagnlegt að byrja á:
set -euo pipefail
pipefail lætur pípuna skila villu ef skref inni í henni mistekst. Við notum þetta með skilningi: grep skilar stöðunni 1 þegar engin samsvörun finnst, sem gæti verið eðlileg niðurstaða í sumum skriftum.
Lokaverkefni: hávaðasama sendingin
Æfing 5
sending.txt inniheldur:
# synthetic CTF data
2026-07-10 user=anna part=2 value=gna
rusl
2026-07-10 user=katla part=1 value=ekki
2026-07-10 user=anna part=1 value=gg{ga
2026-07-10 user=anna part=4 value=xti}
2026-07-10 user=anna part=3 value=te
2026-07-10 user=anna part=x value=VILLA
2026-07-10 user=katla part=2 value=svar
Verkefnið segir:
Veldu notandann sem á fjóra tölusetta hluta, raðaðu eftir hlutanúmeri og límdu
valuesaman.
Skrifaðu lausn sem staðfestir sniðið og skilar niðurstöðu.
Möguleg lausn við æfingu 5
Python hentar vel því við þurfum að staðfesta, hópa, raða og líma:
import re
from collections import defaultdict
mynstur = re.compile(
r"^2026-07-10 user=(?P<user>[a-z]+) "
r"part=(?P<part>[0-9]+) value=(?P<value>\S+)$"
)
hlutar = defaultdict(dict)
ogildar = []
with open("sending.txt", encoding="utf-8") as f:
for nr, lina in enumerate(f, start=1):
lina = lina.rstrip("\n")
if not lina or lina.startswith("#"):
continue
m = mynstur.fullmatch(lina)
if not m:
ogildar.append((nr, lina))
continue
user = m.group("user")
part = int(m.group("part"))
value = m.group("value")
if part in hlutar[user]:
raise ValueError(f"Tvítekið part={part} hjá {user}")
hlutar[user][part] = value
for user, gildi in hlutar.items():
if sorted(gildi) == [1, 2, 3, 4]:
svar = "".join(gildi[nr] for nr in range(1, 5))
print(user, svar)
print("Ógildar línur:", ogildar)
Niðurstaðan er:
anna gg{gagnatexti}
Ógildu línurnar eru rusl og línan með part=x. Katla hefur aðeins tvo hluta og uppfyllir því ekki skilyrðið.
Við staðfestum að Anna hafi nákvæmlega hlutana 1–4 og höfnum tvíteknum hlutanúmerum. Einföld sort-skipun án þessara athugana gæti falið gölluð gögn.
Samantekt
Í þessum kafla lærðum við að:
- byrja á skýrri spurningu og skoða sýni af inntakinu,
- tengja lítil tól með pípum,
- nota
grepog reglulegar segðir til að sía og draga út, - raða áður en
uniq -ctelur tíðni, - nota tölulega eða fyrirsjáanlega röðun þegar þess þarf,
- velja
cutogawkfyrir einföld afmörkuð snið, - nota Python
csvfyrir raunverulegar CSV-reglur, - sía og umbreyta JSON með
jq, - staðfesta heilar línur með
re.fullmatch(), - telja og leysa jafntefli með skýrum reglum,
- mæla hve margar línur voru gildar, hunsaðar eða ógildar,
- varðveita frumgögn og skrá allar hreinsunarreglur.
Í næsta kafla breytum við verkefnalýsingum í reiknirit, hermum eftir litlum ferlum og leysum afmarkaðar forritunar- og rökþrautir.