Heb je ooit een tekst met bijna een half miljoen regels (iets meer dan 300 MB) gecontroleerd op niet gesloten haken. M.a.w. wordt elke geopende haakje ook terug gesloten?
Elke regel bevat bovendien 77 velden, gescheiden door tab-tekens. We zoeken dus naar niet afgesloten haken binnen elk veld. De volgende opdracht klaarde de klus op een Intel i3 dual core processor met 4 GB RAM geheugen in 1 minuut en 13 seconden. Niet slecht als je weet dat je daarvoor 36.036.077 velden moet controleren.
awk -F"\t" '{for (i=1;i<=NF;i++) if (split($i,a,"(") != split($i,b,")")) {print NR": "$0; next}}' tekstbestand
Om uit te leggen hoe de opdracht werkt, maken we een testbestand met drie door tab-tekens gescheiden velden.
dany@pindabook:~> cat > demo a(aaa) bbbb (cccc( dddd e(e(ee) fff)f gg(g)g hhh(h ii(i)i
Om het demo bestand af te sluiten en weg te schrijven, druk je Ctrl + d.
AWK heeft een split functie met drie of vier argumenten. Het eerste argument bevat de te splitsen tekenreeks. Het tweede argument bevat de naam van het array waarin de gesplitste delen worden opgeslagen. Het derde argument bevat het teken (of tekenreeks) die een splitsing veroorzaken. Het vierde optioneel argument bevat de naam van het array waarin de tekens van elke splitsing die de splitsing veroorzaakten wordt opgeslagen. Uiteindelijk geeft split het aantal gesplitste delen terug.
Met volgende opdracht wordt elke regel gesplitst bij het ( teken en wordt per regel het aantal gesplitste delen weergegeven:
dany@pindabook:~> awk '{print split($0,a,"(")}' demo
4
3
4
Je merkt dat AWK onze eerste regel in vier delen heeft gesplitst, namelijk a
, aaa)[tab]bbbb[tab]
, cccc
en een leeg deel na de laatste (.
AWK maakt voor het uitvoeren van de opdracht de array a eerst leeg.
Herhalen we de opdracht met splitsen op een ) teken, dan bekomen we:
dany@pindabook:~> awk '{print split($0,a,")")}' demo
2
3
3
Nu kunnen we AWK het aantal in een regel gevonden geopende en gesloten haken met elkaar laten vergelijken. Zijn er niet evenveel geopende als gesloten haken, dan wordt het regelnummer en zijn inhoud weergegeven.
dany@pindabook:~> awk '{if (split($0,a,"(") != split($0,a,")")) print NR": "$0}' demo
1: a(aaa) bbbb (cccc(
3: gg(g)g hhh(h ii(i)i
Regel twee bevat echter twee velden met niet afgesloten haken.
Om binnen de velden te kijken wordt een for
lus gebruikt.
Als in een veld een niet afgesloten haakje wordt gevonden, wordt het regelnummer en zijn inhoud weergegeven.
Met next wordt dan de volgende regel onderzocht.
dany@pindabook:~> awk -F"\t" '{for (i=1;i<=NF;i++) if (split($i,a,"(") != split($i,b,")")) {print NR": "$0; next}}' demo
1: a(aaa) bbbb (cccc(
2: dddd e(e(ee) fff)f
3: gg(g)g hhh(h ii(i)i
Deze opdracht is niet perfect, het vindt geen verkeerd geplaatste haakjes, zoals in )kkk(k
.
Daarvoor breiden we ons demo bestand uit met een extra regel (\t
zorgt voor Tab ruimte):
dany@pindabook:~>echo -e "jjjj\t)kkk(k\tllll" >> demo
dany@pindabook:~>cat demo
a(aaa) bbbb (cccc( dddd e(e(ee) fff)f gg(g)g hhh(h ii(i)i jjjj )kkk(k llll
Met een AWK opdracht met een ingewikkelde reguliere expressie gaan we op zoek naar verkeerd geplaatste haken:
dany@pindabook:~> awk -F"\t" '{for (i=1;i<=NF;i++) if ($i ~ /^[^()]*\)[^()]*\(/) print NR": "$0}' demo
4: jjjj )kkk(k llll
Met de sed editor kunnen we de betreffende regel weergeven om deze te onderzoeken:
dany@pindabook:~> sed -n '4p' demo
jjjj )kkk(k llll
En te repareren:
dany@pindabook:~>sed -i -e "4s/)kkk(/(kkk)/" demo
dany@pindabook:~>sed -n '4p' demo
jjjj (kkk)k llll
Wie regelmatig programmeert of webpagina's maakt, weet hoe belangrijk haakjes zijn (Adobe noemt zijn webeditor niet voor niets Brackets). Laten we onze AWK opdracht los op een webpagina om de {} paren te controleren, dan bekom je het volgende resultaat:
dany@pindabook:~> awk -F"\t" '{for (i=1;i<=NF;i++) if (split($i,a,"{") != split($i,b,"}")) {print NR": "$0; next}}' Documenten/Web/index.html
44: (function() {
48: })();
96: function verzenden(){
102: }
Hoewel de geteste webpagina geen fouten bevat, worden toch vier regels weergegeven. Dit komt doordat de haken geopend worden op twee regels (44 en 96) en terug gesloten op andere regels (48 en 102). Dit is voor een programmeur of ontwerper al een hele hulp.
Het kan echter nog beter met het volgende script:
#!/bin/bash # Itentify the script bname="$(basename "$0")" # Make a work dir wdir="/tmp/$USER/$bname" [[ ! -d "$wdir" ]] && mkdir -p "$wdir" # Arg1: The bracket pair 'string' pair="$1" # pair='[]' # test # pair='<>' # test # pair='{}' # test # pair='()' # test # Arg2: The input file to test ifile="$2" # Build a test source file # ifile="$wdir/$bname.in" # cp /dev/null "$ifile" # while IFS= read -r line ;do # echo "$line" >> "$ifile" # done <<EOF #AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA #[ ] [ [ [ #< > < # < > # < > > > #----+----1----+----2----+----3----+----4----+----5----+----6 #{ } { } } } } #( ) ( ( ( ) ) #ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ #EOF echo "File = $ifile" # Count how many: Left, Right, and Both left=${pair:0:1} rght=${pair:1:1} echo "Pair = $left$rght" # Make a stripped-down 'skeleton' of the source file - brackets only skel="/tmp/$USER/$bname.skel" cp /dev/null "$skel" # Make a String Of Brackets file ... (It is tricky manipulating bash strings with [].. sed 's/[^'${rght}${left}']//g' "$ifile" > "$skel" < "$skel" tr -d '\n' > "$skel.str" Left=($(<"$skel.str" tr -d "$left" |wc -m -l)); LeftCt=$((${Left[1]}-${Left[0]})) Rght=($(<"$skel.str" tr -d "$rght" |wc -m -l)); RghtCt=$((${Rght[1]}-${Rght[0]})) yBkts=($(sed -e "s/\(.\)/ \1 /g" "$skel.str")) BothCt=$((LeftCt+RghtCt)) eleCtB=${#yBkts[@]} echo if (( eleCtB != BothCt )) ; then echo "ERROR: array Item Count ($eleCtB)" echo " should equal BothCt ($BothCt)" exit 1 else grpIx=0 # Keep track of Groups of nested pairs eleIxFir[$grpIx]=0 # Ix of First Bracket in a specific Group eleCtL=0 # Count of Left brackets in current Group eleCtR=0 # Count of Right brackets in current Group errIx=-1 # Ix of an element in error. for (( eleIx=0; eleIx < eleCtB; eleIx++ )) ; do if [[ "${yBkts[eleIx]}" == "$left" ]] ; then # Left brackets are 'okay' until proven otherwise ((eleCtL++)) # increment Left bracket count else ((eleCtR++)) # increment Right bracket count # Right brackets are 'okay' until their count exceeds that of Left brackets if (( eleCtR > eleCtL )) ; then echo echo "ERROR: MIS-matching Right \"$rght\" in Group $((grpIx+1)) (at Bracket $((eleIx+1)) overall)" errType=$rght errIx=$eleIx break elif (( eleCtL == eleCtR )) ; then echo "*INFO: Group $((grpIx+1)) contains $eleCtL matching pairs" # Reset the element counts, and note the first element Ix for the next group eleCtL=0 eleCtR=0 ((grpIx++)) eleIxFir[$grpIx]=$((eleIx+1)) fi fi done # if (( eleCtL > eleCtR )) ; then # Left brackets are always potentially valid (until EOF)... # so, this 'error' is the last element in array echo echo "ERROR: *END-OF-FILE* encountered after Bracket $eleCtB." echo " A Left \"$left\" is un-paired in Group $((grpIx+1))." errType=$left unpairedCt=$((eleCtL-eleCtR)) errIx=$((${eleIxFir[grpIx]}+unpairedCt-1)) echo " Group $((grpIx+1)) has $unpairedCt un-paired Left \"$left\"." echo " Group $((grpIx+1)) begins at Bracket $((eleIxFir[grpIx]+1))." fi # On error, get Line and Column numbers if (( errIx >= 0 )) ; then errLNum=0 # Source Line number (current). eleCtSoFar=0 # Count of bracket-elements in lines processed so far. errItemNum=$((errIx+1)) # error Ix + 1 (ie. "1 based") # Read the skeketon file to find the error line-number while IFS= read -r skline ; do ((errLNum++)) brackets="${skline//[^"${rght}${left}"]/}" # remove whitespace ((eleCtSoFar+=${#brackets})) if (( eleCtSoFar >= errItemNum )) ; then # We now have the error line-number # ..now get the relevant Source Line excerpt=$(< "$ifile" tail -n +$errLNum |head -n 1) # Homogenize the brackets (to be all "Left"), for easy counting mogX="${excerpt//$rght/$left}"; mogXCt=${#mogX} # How many 'Both' brackets on the error line? if [[ "$errType" == "$left" ]] ; then # R-Trunc from the error element [inclusive] ((eleTruncCt=eleCtSoFar-errItemNum+1)) for (( ele=0; ele<eleTruncCt; ele++ )) ; do mogX="${mogX%"$left"*}" # R-Trunc (Lazy) done errCNum=$((${#mogX}+1)) else # errType=$rght mogX="${mogX%"$left"*}" # R-Trunc (Lazy) errCNum=$((${#mogX}+1)) fi echo " see: Line, Column ($errLNum, $errCNum)" echo " ----+----1----+----2----+----3----+----4----+----5----+----6----+----7" printf "%06d $excerpt\n\n" $errLNum break fi done < "$skel" else echo "*INFO: OK. All brackets are paired." fi fi exit
Dit wordt het resultaat: