¿Cómo encontrar corchetes sin igual en un archivo de texto?

32

Hoy aprendí que puedo usar perl -c filenamepara encontrar llaves sin igual {} en archivos arbitrarios, no necesariamente scripts de Perl. El problema es que no funciona con otros tipos de corchetes () [] y tal vez <>. También tuve experimentos con varios complementos de Vim que dicen ayudar a encontrar soportes incomparables, pero hasta ahora no son tan buenos.

¡Tengo un archivo de texto con bastantes paréntesis y falta uno de ellos! ¿Hay algún programa / script / vim plugin / lo que sea que me pueda ayudar a identificar el soporte sin igual?

phunehehe
fuente

Respuestas:

22

En Vim, puede usar [y ]para viajar rápidamente al soporte no coincidente más cercano del tipo ingresado en la siguiente pulsación de tecla.

Entonces [{lo llevará de regreso al "{" incomparable más cercano; ])te llevaría adelante al ")" sin igual más cercano, y así sucesivamente.

Shadur
fuente
Genial, esto es perfecto para mí. Estoy a punto de aceptar esta respuesta, pero solo espero ver si hay una herramienta de procesamiento de texto que pueda analizar esto.
phunehehe
66
También agregaré que en vim puede usar% (Shift 5, en los EE. UU.) Para encontrar inmediatamente el soporte correspondiente para el que está usando .
atroon
@atroon Ooo, bien. Todavía no lo sabía. Me encanta stackexchange a veces. :)
Shadur
es <kbd> [</kbd> y <kbd>] </kbd> realmente saltando al
wirrbel
Pasé casi un día revisando 4000 líneas tratando de encontrar lo que faltaba} en R y esta fue la respuesta. De nuevo, gracias VIM! Pero creo que este es un buen argumento para dividir los archivos de código fuente en fragmentos más pequeños.
Thomas Browne
7

Actualización 2:
El siguiente script ahora imprime el número de línea y la columna de un paréntesis no coincidente . Procesa un tipo de soportes por exploración (es decir. '[]' '<>' '{}' '()' ...)
Las secuencias de comandos identifica la primera , soporte derecho sin igual , o la primera de cualquier soporte izquierdo ONU-emparejado ... Al detectar un erroe, sale con los números de línea y columna

Aquí hay algunos resultados de muestra ...


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

Aquí está el guión ...


#!/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
fuente
¡Este guión es brillante!
Jonathan Dumaine
1
Esto es increíble, pero parece que siempre se imprime Line, Column (8, 10)sin importar en qué archivo lo pruebo. También mogXCt=${#mogX}está configurado pero no se usa en ningún lado.
Clayton Dukes
5

La mejor opción es vim / gvim según lo identificado por Shadur, pero si desea un script, puede verificar mi respuesta a una pregunta similar en Stack Overflow . Repito toda mi respuesta aquí:

Si lo que intenta hacer se aplica a un lenguaje de propósito general, entonces este es un problema no trivial.

Para comenzar, tendrá que preocuparse por los comentarios y las cadenas. Si desea verificar esto en un lenguaje de programación que utiliza expresiones regulares, esto hará que su búsqueda sea más difícil nuevamente.

Entonces, antes de que pueda entrar y darle algún consejo sobre su pregunta, necesito conocer los límites de su área problemática. Si puede garantizar que no hay cadenas, ni comentarios ni expresiones regulares de las que preocuparse, o más genéricamente en ninguna parte del código que los corchetes puedan usarse de otra manera que no sea para los usos para los que está verificando que estén equilibrados, esto Hacer la vida mucho más simple.

Sería útil conocer el idioma que desea verificar.


Si tomo la hipótesis de que no hay ruido, es decir, que todos los paréntesis son paréntesis útiles, mi estrategia sería iterativa:

Simplemente buscaría y eliminaría todos los pares de corchetes internos: aquellos que no contienen corchetes dentro. Esto se logra mejor al contraer todas las líneas en una sola línea larga (y encontrar un mecanismo para agregar referencias de línea, en caso de que necesite obtener esa información). En este caso, la búsqueda y reemplazo es bastante simple:

Requiere una matriz:

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

Y un bucle a través de esos elementos:

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

Mi archivo de prueba es el siguiente:

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

Mi script completo (sin referencia de línea) es el siguiente:

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

La salida de ese script se detiene en los usos ilegales más internos de los corchetes. Pero cuidado: 1 / este script no funcionará con corchetes en los comentarios, expresiones regulares o cadenas, 2 / no informa en qué parte del archivo original se encuentra el problema, 3 / aunque eliminará todos los pares equilibrados que se detiene en lo más interno condiciones de error y mantiene todos los paréntesis envolventes.

El punto 3 / es probablemente un resultado explotable, aunque no estoy seguro del mecanismo de informes que tenía en mente.

El punto 2 / es relativamente fácil de implementar, pero lleva más de unos minutos trabajar para producirlo, así que lo dejaré a usted para que lo descubra.

El punto 1 / es el complicado porque entras en un reino completamente nuevo de comienzos y finales competitivos a veces anidados, o reglas especiales de comillas para personajes especiales ...

asoundmove
fuente
1
Gracias me salvaste. Tenía una llave no coincidente en un archivo json de 30k líneas.
I82Much