Come trovare parentesi senza pari in un file di testo?


32

Oggi ho imparato che posso usare perl -c filenameper trovare parentesi graffe senza pari {} in file arbitrari, non necessariamente script Perl. Il problema è che non funziona con altri tipi di parentesi () [] e forse <>. Ho anche fatto esperimenti con diversi plugin Vim che sostengono di aiutare a trovare parentesi senza pari, ma finora non così buono.

Ho un file di testo con alcune parentesi e ne manca una! Esiste un programma / script / plugin vim / qualunque cosa possa aiutarmi a identificare la parentesi senza pari?

Risposte:


22

In Vim è possibile utilizzare [e ]spostarsi rapidamente alla parentesi senza pari più vicina del tipo immesso nella sequenza di tasti successiva.

Quindi [{ti riporterà al più vicino "{" senza pari; ])ti porterebbe al più vicino ")" senza pari, e così via.


Fantastico, questo è perfetto per me. Sto per accettare questa risposta, ma sto solo aspettando di vedere se esiste uno strumento di elaborazione del testo in grado di analizzarlo.
phunehehe,

6
Aggiungerò anche che in vim puoi usare% (Shift 5, negli Stati Uniti) per trovare immediatamente la parentesi corrispondente a quella su cui ti trovi.
atroon,

@atroon Ooo, bello. Non lo sapevo ancora io. Adoro stackexchange a volte. :)
Shadur,

<kbd> [</kbd> e <kbd>] </kbd>
stanno

Ho trascorso quasi un giorno a percorrere 4000 righe cercando di trovare i} mancanti in R e questa era la risposta. Ancora una volta, grazie VIM! Ma penso che questo sia un buon argomento per dividere i file di codice sorgente in blocchi più piccoli.
Thomas Browne,

7

Aggiornamento 2:
il seguente script ora stampa il numero di riga e la colonna di una parentesi errata . Esso elabora un tipo di staffa per scansione (es. '[]' '<>' '{}' '()' ...)
I identifica di script la prima , staffa a destra senza pari , o il primo di una staffa di sinistra non-accoppiato ... Al rilevamento di un erroe, esce con i numeri di riga e colonna

Ecco alcuni esempi di output ...


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  (   )    (         (         (     )   )                    

Ecco la sceneggiatura ...


#!/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

Questa sceneggiatura è geniale!
Jonathan Dumaine,

1
Questo è fantastico, ma sembra sempre stampare Line, Column (8, 10)indipendentemente dal file su cui lo provo. Inoltre mogXCt=${#mogX}è impostato ma non utilizzato da nessuna parte.
Clayton Dukes

5

L'opzione migliore è vim / gvim come identificato da Shadur, ma se vuoi uno script, puoi controllare la mia risposta a una domanda simile su Stack Overflow . Ripeto tutta la mia risposta qui:

Se ciò che stai cercando di fare si applica a un linguaggio generico, allora questo è un problema non banale.

Per cominciare dovrai preoccuparti di commenti e stringhe. Se vuoi verificarlo su un linguaggio di programmazione che utilizza espressioni regolari, questo renderà di nuovo più difficile la tua ricerca.

Quindi, prima di poter entrare e darti qualche consiglio sulla tua domanda, devo conoscere i limiti della tua area problematica. Se puoi garantire che non ci sono stringhe, commenti o espressioni regolari di cui preoccuparti - o più genericamente da nessuna parte nel codice che le parentesi possono essere usate se non per gli usi per i quali stai controllando che siano bilanciate - questo farà rendere la vita molto più semplice.

Conoscere la lingua che si desidera controllare sarebbe utile.


Se prendo l'ipotesi che non ci sia rumore, cioè che tutte le parentesi siano parentesi utili, la mia strategia sarebbe iterativa:

Vorrei semplicemente cercare e rimuovere tutte le coppie di parentesi interne: quelle che non contengono parentesi all'interno. Questo è fatto meglio comprimendo tutte le linee su una singola linea lunga (e trovare un meccanismo per aggiungere riferimenti di linea, nel caso in cui fosse necessario ottenere tali informazioni). In questo caso la ricerca e la sostituzione è piuttosto semplice:

Richiede un array:

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

E un ciclo attraverso quegli elementi:

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

Il mio file di test è il seguente:

#!/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)
}

Il mio script completo (senza riferimento alla riga) è il seguente:

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
    }
  '

L'output di quello script si interrompe negli usi più interni illegali delle parentesi. Ma attenzione: 1 / questo script non funziona con parentesi nei commenti, espressioni regolari o stringhe, 2 / non riporta dove si trova il problema nel file originale, 3 / sebbene rimuoverà tutte le coppie bilanciate si ferma al più interno condizioni di errore e mantiene tutte le parentesi coinvolgenti.

Il punto 3 / è probabilmente un risultato sfruttabile, anche se non sono sicuro del meccanismo di segnalazione che avevi in ​​mente.

Il punto 2 / è relativamente facile da implementare ma richiede più di qualche minuto per essere prodotto, quindi lascerò a te capire.

Il punto 1 / è difficile perché si entra in un regno completamente nuovo di inizi e finali a volte annidati concorrenti o regole speciali di quotazione per personaggi speciali ...


1
Grazie, mi hai salvato. Aveva una parentesi graffa non corrispondente in un file json di 30k line.
I82 Molto
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.