Como encontrar colchetes inigualáveis ​​em um arquivo de texto?

32

Hoje aprendi que posso usar perl -c filenamepara encontrar colchetes {} incomparáveis ​​em arquivos arbitrários, não necessariamente scripts Perl. O problema é que ele não funciona com outros tipos de colchetes () [] e talvez <>. Eu também tive experiências com vários plugins do Vim que pretendem ajudar a encontrar colchetes incomparáveis, mas até agora não são tão bons.

Eu tenho um arquivo de texto com alguns colchetes e um deles está faltando! Existe algum plugin de programa / script / vim / o que quer que possa me ajudar a identificar o colchete inigualável?

phunehehe
fonte

Respostas:

22

No Vim, você pode usar [e ]viajar rapidamente para o colchete não correspondido mais próximo do tipo digitado no próximo pressionamento de tecla.

Então [{, você voltará para o "{" mais próximo sem igual; ])levaria você à frente do ")" mais próximo sem igual) e assim por diante.

Shadur
fonte
Ótimo, isso é perfeito para mim. Estou prestes a aceitar esta resposta, mas apenas esperando para ver se existe uma ferramenta de processamento de texto que possa analisar isso.
phunehehe
6
Acrescentarei também que no vim você pode usar% (Shift 5, nos EUA) para encontrar imediatamente o colchete correspondente ao que você está usando.
atroon 29/03
@atroon Ooo, legal. Eu ainda não sabia disso. Eu amo stackexchange às vezes. :)
Shadur
é <kbd> [</ kbd> e <kbd>] </ kbd> realmente saltar para o
wirrbel
Passei quase um dia percorrendo 4000 linhas tentando encontrar os desaparecidos} em R e essa foi a resposta. Mais uma vez, obrigado VIM! Mas acho que esse é um bom argumento para dividir arquivos de código-fonte em partes menores.
Thomas Browne
7

Atualização 2:
O script a seguir agora imprime o número da linha e a coluna de um colchete incorreto . Ele processa um tipo de suporte por varredura (ie. '[]' '<>' '{}' '()' ...)
Os identifica de script do primeiro , colchete direito inigualável , ou o primeiro de qualquer suporte esquerdo emparelhado-un ... Ao detectar um erro, ele sai com os números de linha e coluna

Aqui está um exemplo de saída ...


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

Aqui está o 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
Peter.O
fonte
Este script é brilhante!
Jonathan Dumaine
1
Isso é incrível, mas parece sempre imprimir, Line, Column (8, 10)não importa em qual arquivo eu o teste. Também mogXCt=${#mogX}está definido, mas não é usado em nenhum lugar.
Clayton Dukes
5

A melhor opção é vim / gvim, conforme identificado por Shadur, mas se você quiser um script, poderá verificar minha resposta para uma pergunta semelhante no Stack Overflow . Repito toda a minha resposta aqui:

Se o que você está tentando fazer se aplica a uma linguagem de uso geral, esse é um problema não trivial.

Para começar, você terá que se preocupar com comentários e strings. Se você quiser verificar isso em uma linguagem de programação que usa expressões regulares, isso tornará sua busca mais difícil novamente.

Portanto, antes que eu possa entrar e dar algum conselho sobre sua pergunta, preciso conhecer os limites da sua área problemática. Se você pode garantir que não há seqüências de caracteres, comentários ou expressões regulares para se preocupar - ou mais genericamente em nenhum lugar do código que os colchetes possam ser usados, exceto para os usos para os quais você está verificando se eles estão equilibrados - isso será tornar a vida muito mais simples.

Conhecer o idioma que você deseja verificar seria útil.


Se eu assumir a hipótese de que não há ruído, ou seja, que todos os colchetes são colchetes úteis, minha estratégia seria iterativa:

Eu simplesmente procuraria e removeria todos os pares de colchetes internos: aqueles que não contêm colchetes no interior. É melhor fazer isso recolhendo todas as linhas em uma única linha longa (e encontre um mecanismo para adicionar referências de linha, caso seja necessário obter essas informações). Nesse caso, a pesquisa e substituição são bem simples:

Requer uma matriz:

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

E um loop através desses elementos:

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

Meu arquivo de teste é o seguinte:

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

Meu script completo (sem referência de linha) é o seguinte:

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

A saída desse script é interrompida nos usos ilegais mais íntimos de colchetes. Mas cuidado: 1 / este script não funcionará com colchetes nos comentários, expressões regulares ou seqüências de caracteres, 2 / não informa onde o problema está localizado no arquivo original, 3 / embora remova todos os pares balanceados que para no interior condições de erro e mantém todos os colchetes.

O ponto 3 / é provavelmente um resultado explorável, embora eu não tenha certeza do mecanismo de relatório que você tinha em mente.

O ponto 2 / é relativamente fácil de implementar, mas leva mais do que alguns minutos para produzir, portanto, deixarei que você decida.

O ponto 1 / é o mais complicado porque você entra em um novo reino de princípios e finais concorrentes, às vezes aninhados, ou regras especiais de citação para caracteres especiais ...

asoundmove
fonte
1
Obrigado, você me salvou. Tinha uma chave incompatível em um arquivo json de 30k linhas.
I82Much