Wie finde ich unvergleichliche Klammern in einer Textdatei?

32

Heute habe ich gelernt, dass ich nicht perl -c filenamepassende geschweifte Klammern {} in beliebigen Dateien finden kann, nicht unbedingt in Perl-Skripten. Das Problem ist, dass es mit anderen Arten von Klammern () [] und vielleicht <> nicht funktioniert. Ich hatte auch Experimente mit verschiedenen Vim-Plugins, die angeblich helfen sollen, nicht passende Klammern zu finden, die aber bisher nicht so gut waren.

Ich habe eine Textdatei mit einigen Klammern, von denen eine fehlt! Gibt es ein Programm / Skript / Vim-Plugin / irgendetwas, das mir helfen kann, die nicht passende Klammer zu identifizieren?

phunehehe
quelle

Antworten:

22

In Vim können Sie [und verwenden, ]um schnell zur nächsten nicht passenden Klammer des Typs zu gelangen, der beim nächsten Tastendruck eingegeben wurde.

So gelangen [{Sie zurück zum nächsten "{"; ])würde Sie zum nächsten unübertroffenen ")" führen und so weiter.

Shadur
quelle
Großartig, das ist perfekt für mich. Ich bin dabei, diese Antwort zu akzeptieren, warte aber nur darauf, ob es ein Textverarbeitungstool gibt, das dies analysieren kann.
Phunehehe
6
Ich werde auch hinzufügen, dass Sie in vim% (Shift 5, in den USA) verwenden können, um sofort die passende Klammer für die zu finden , auf der Sie sind.
am
@atroon Ooo, schön. Das wusste ich noch nicht. Ich liebe manchmal Stackexchange. :)
Shadur
springt <kbd> [</ kbd> und <kbd>] </ kbd> wirklich zum
wirrbel
Ich verbrachte fast einen Tag damit, 4000 Zeilen zu durchsuchen, um das fehlende} in R zu finden, und dies war die Antwort. Nochmals vielen Dank VIM! Aber ich denke, das ist ein gutes Argument, um Quellcode-Dateien in kleinere Teile aufzuteilen.
Thomas Browne
7

Update 2:
Das folgende Skript druckt jetzt die Zeilennummer und die Spalte einer falsch zugeordneten Klammer aus . Er verarbeitet eine Haltewinkelart pro Abtastung (dh. ‚[]‘ ‚<>‘ ‚{}‘ ‚()‘ ...)
Das Skript identifiziert die erste , nicht angepasste rechte Klammer oder die ersten einer un-paarige linken Klammer ... Beim Erkennen eines Fehlers werden die Zeilen- und Spaltennummern angezeigt

Hier ist eine Beispielausgabe ...


File = /tmp/fred/test/test.in
Pair = ()

*INFO:  Group 1 contains 1 matching pairs

ERROR: *END-OF-FILE* encountered after Bracket 7.
        A Left "(" is un-paired in Group 2.
        Group 2 has 1 un-paired Left "(".
        Group 2 begins at Bracket 3.
  see:  Line, Column (8, 10)
        ----+----1----+----2----+----3----+----4----+----5----+----6----+----7
000008  (   )    (         (         (     )   )                    

Hier ist das Skript ...


#!/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
Peter.O
quelle
Dieses Skript ist brillant!
Jonathan Dumaine
1
Das ist großartig, aber es scheint immer zu drucken, Line, Column (8, 10)egal welche Datei ich anprobieren. Wird auch mogXCt=${#mogX}gesetzt aber nirgends benutzt.
Clayton Dukes
5

Die beste Möglichkeit ist vim / gvim wie Shadur identifiziert, aber wenn Sie ein Skript möchten, können Sie überprüfen , meine Antwort auf eine ähnliche Frage auf Stack - Überlauf . Ich wiederhole hier meine ganze Antwort:

Wenn das, was Sie versuchen, auf eine allgemeine Sprache zutrifft, ist dies ein nicht triviales Problem.

Zunächst müssen Sie sich um Kommentare und Zeichenfolgen kümmern. Wenn Sie dies in einer Programmiersprache überprüfen möchten, die reguläre Ausdrücke verwendet, wird dies Ihre Suche erneut erschweren.

Bevor ich Sie zu Ihrer Frage beraten kann, muss ich die Grenzen Ihres Problembereichs kennen. Wenn Sie garantieren können, dass es keine Zeichenfolgen, keine Kommentare und keine regulären Ausdrücke gibt, über die Sie sich Sorgen machen müssen, oder generell nirgendwo im Code, wo Klammern möglicherweise anders als für die Zwecke verwendet werden können, für die Sie überprüfen, ob sie ausgeglichen sind, wird dies der Fall sein das leben viel einfacher machen.

Es wäre hilfreich zu wissen, welche Sprache Sie überprüfen möchten.


Wenn ich die Hypothese annehme, dass es kein Rauschen gibt, dh dass alle Klammern nützliche Klammern sind, wäre meine Strategie iterativ:

Ich würde einfach alle inneren Klammerpaare suchen und entfernen: diejenigen, die keine Klammern enthalten. Dies geschieht am besten, indem Sie alle Zeilen zu einer einzigen langen Zeile zusammenfassen (und einen Mechanismus zum Hinzufügen von Zeilenreferenzen finden, falls Sie diese Informationen benötigen). In diesem Fall ist das Suchen und Ersetzen ziemlich einfach:

Es erfordert ein Array:

B["("]=")"; B["["]="]"; B["{"]="}"

Und eine Schleife durch diese Elemente:

for (b in B) {gsub("[" b "][^][(){}]*[" B[b] "]", "", $0)}

Meine Testdatei lautet wie folgt:

#!/bin/awk

($1 == "PID") {
  fo (i=1; i<NF; i++)
  {
    F[$i] = i
  }
}

($1 + 0) > 0 {
  count("VIRT")
  count("RES")
  count("SHR")
  count("%MEM")
}

END {
  pintf "VIRT=\t%12d\nRES=\t%12d\nSHR=\t%12d\n%%MEM=\t%5.1f%%\n", C["VIRT"], C["RES"], C["SHR"], C["%MEM"]
}

function count(c[)
{
  f=F[c];

  if ($f ~ /m$/)
  {
    $f = ($f+0) * 1024
  }

  C[c]+=($f+0)
}

Mein vollständiges Skript (ohne Zeilenbezug) lautet wie folgt:

cat test-file-for-brackets.txt | \
  tr -d '\r\n' | \
  awk \
  '
    BEGIN {
      B["("]=")";
      B["["]="]";
      B["{"]="}"
    }
    {
      m=1;
      while(m>0)
      {
        m=0;
        for (b in B)
        {
          m+=gsub("[" b "][^][(){}]*[" B[b] "]", "", $0)
        }
      };
      print
    }
  '

Die Ausgabe dieses Skripts stoppt bei der innersten illegalen Verwendung von Klammern. Aber Vorsicht: 1 / Dieses Skript funktioniert nicht mit eckigen Klammern in Kommentaren, regulären Ausdrücken oder Zeichenfolgen. 2 / Es meldet nicht, wo sich das Problem in der Originaldatei befindet. 3 / Obwohl alle ausgeglichenen Paare entfernt werden, die es im Innersten stoppt Fehlerbedingungen und behält alle englischen Klammern.

Punkt 3 / ist wahrscheinlich ein verwertbares Ergebnis, obwohl ich mir nicht sicher bin, welchen Berichtsmechanismus Sie im Sinn hatten.

Punkt 2 / ist relativ einfach zu implementieren, benötigt jedoch mehr als ein paar Minuten Arbeit, sodass ich es Ihnen überlassen werde, dies herauszufinden.

Punkt 1 / ist schwierig, weil Sie einen völlig neuen Bereich konkurrierender, manchmal verschachtelter Anfänge und Enden oder spezieller Anführungsregeln für Sonderzeichen betreten ...

asoundmove
quelle
1
Danke, du hast mich gerettet. Hatte eine nicht übereinstimmende Klammer in einer JSON-Datei mit 30.000 Zeilen.
Viel