AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / unix / Perguntas / 790660
Accepted
Thierry Blanc
Thierry Blanc
Asked: 2025-02-06 18:15:46 +0800 CST2025-02-06 18:15:46 +0800 CST 2025-02-06 18:15:46 +0800 CST

removendo instruções de chaves contendo chaves aninhadas dentro

  • 772

Um problema típico do látex:

\SomeStyle{\otherstyle{this is the \textit{nested part} some more text...}}

Agora eu quero remover tudo \SomeStyle{...}, mas não o conteúdo. O conteúdo contém chaves aninhadas. A linha acima deve se tornar:

\otherstyle{this is the \textit{nested part} some more text...}

Questões:

  1. Algum editor de LaTeX oferece uma maneira de fazer isso?
  2. Qual editor/script faz isso?
  3. Como fazer isso com sed? [🤓]

Minha solução é um script bash usando sed.

  1. preparar texto: marcar substituir string com ascii bell, adicionar nova linha após cada chave
  2. loop: find { -> adiciona X ao espaço de espera, find } -> remove X do espaço de espera, mantém o espaço vazio -> remove o fechamento }
  3. restaurar novas linhas e sino ascii para o anterior

O script funciona, mas falha com: \badstyle{w}\badstyle{o}\badstyle{r}\badstyle{d} Ele se tornará: wo}rd}

a ramificação para :f parece não funcionar.

F=$(sed 's|\\|\\\\|g;s|{|\\{|g' <<< "$1"  )

# mark all removestrings with ascii bell and newline
# add newline after each { and }  
SEDpre='
s|'"$F"'|\a%\n|g

s|\{|\{\n|g
s|\}|\}\n|g
'


SEDpost='
:a;N;$!ba;
s|\a%\n||g

s|\{\n|\{|g
s|\}\n|\}|g
'

# count the brackets
SED='
/\a%/{
:a
        n
:f
        /\{/{x;s|$|X|;x;ba}
        /\}/{x;
                s|X||;
                /^$/{x;bb}
                x
                ba
            }
}
b
:b  
/\}/{   
    s|\}||;
    N;
    s|\n||;
    /\a%/bf
     }
'

sed -r -E  "$SEDpre"  "$2"  | sed -rE "$SED"  | sed -rE "$SEDpost" 
text-processing
  • 5 5 respostas
  • 529 Views

5 respostas

  • Voted
  1. Best Answer
    Stéphane Chazelas
    2025-02-07T00:07:43+08:002025-02-07T00:07:43+08:00

    A abordagem típica é usar perla capacidade de expressão regular recursiva de :

    perl -0777 -pe 's/\\SomeStyle(\{((?:(?1)|[^{}])*)\})/$2/gs' file.tex
    

    Ou se você tiver que contabilizar chaves escapadas como \{(e \escapadas como \\)¹

    perl -0777 -pe 's/\\SomeStyle(\{((?:(?1)|\\.|[^\\{}])*+)\})/$2/gs' file.tex
    

    Onde substituímos [^{}]*por (?:\\.|[^{}\\])*para corresponder \anycharacter(incluindo \\, \{e \}com os quais nos importamos aqui), além de caracteres diferentes de \, {, e }. (?:...)é a forma não capturável de (...).

    (adicione -iopção para editar o arquivo in-place).

    Acima (?1)é como inserir a expressão regular no primeiro par de (...), então (\{((?:(?1)|\\.|[^\\{}])*+)\})nesse ponto.

    Se o \SomeStyle{...}s pode ser aninhado como em:

    \SomeStyle{\otherstyle{this is
    the \SomeStyle{\textit{nested part} some} more text...}}
    

    Para ser alterado para:

    \otherstyle{this is
    the \textit{nested part} some more text...}
    

    Então mude para:

    perl -0777 -pe '
      while(s/\\SomeStyle(\{((?:(?1)|\\.|[^\\{}])*+)\}){}' file.tex
    

    O que repetirá o processo, substituindo os externos primeiro até que nenhuma correspondência seja encontrada.

    Para fazer isso para estilos e arquivos arbitrários:

    #! /bin/sh -
    [ "$#" -ge 2 ] || {
      printf>&2 '%s\n' "Usage: $0 <style> <file> [<file>...]"
      exit 1
    }
    style=$1; shift
    exec perl -0777 -spi -e '
      while(s/\\\Q$style\E(\{((?:(?1)|\\.|[^\\{}])*+)\})/$2/gs) {}
      ' -- -style="$style" -- "$@"
    

    Com sed, assumindo uma implementação onde toda a entrada pode caber no espaço de padrões, uma abordagem (também lidando com entradas aninhadas, começando com as internas neste caso) poderia ser:

    sed '
      :a
      $!{
        # slurp the whole input into the pattern space
        N; ba
      }
      # using _ as an escape character to escape { as _l and
      # } as _r below. So escape itself as _u first:
      s/_/_u/g
      :b
      # process the \SomeStyle{...}s that contain no unescaped {}:
      s/\\SomeStyle{\([^{}]*\)}/\1/g; tb
      # replace inner {...} to _l..._r and loop:
      s/{\([^{}]*\)}/_l\1_r/g; tb
      # undo escaping:
      s/_l/{/g; s/_r/}/g; s/_u/_/g' file.tex
    

    (mesmo tipo de abordagem usada em Removendo aspas de texto (possivelmente aninhadas) na linha de comando e alguns outros aqui).

    Algumas sedimplementações copiaram o Perl -ipara edição no local, mas esteja ciente de que em algumas (FreeBSD e derivados), você precisa -i ''fazer edição no local sem fazer backup do original. -i.backfuncionaria em todas as implementações que têm um -i(e em Perl) e salvam o original como file.tex.back.

    Parece que você sedé GNU sed, pois está usando vários GNUismos, e o GNU sedoferece suporte -ia isso perl e, até onde sei, não há um limite além da memória disponível no tamanho do espaço do padrão.

    Para contabilizar chaves escapadas como \{(e \escapadas como \\)¹, você pode usar a opção agora padrão -E(preferível à específica do GNU -r) para alternar para expressões regulares estendidas que têm um |operador de alternância, embora observe que {também se torna um operador regexp then e precisa ser escapado quando estiver fora de [...], e agrupar+capturar mudanças de \(...\)para (...):

    sed -E '
      :a
      $!{
        # slurp the whole input into the pattern space
        N; ba
      }
      # using _ as an escape character to escape { as _l and
      # } as _r below. So escape itself as _u first:
      s/_/_u/g
      :b
      # process the \SomeStyle{...} that contain no {}:
      s/\\SomeStyle\{((\\.|[^{}\\])*)\}/\1/g; tb
      # replace inner {...} to _l..._r and loop:
      s/\{((\\.|[^{}\\])*)\}/_l\1_r/g; tb
      # undo escaping:
      s/_l/{/g; s/_r/}/g; s/_u/_/g' file.tex
    

    ¹ ainda ignorando a possibilidade de que possa haver \\SomeStyle{something}, não manipulando comentários ou \verb|...|... Cobrir esses problemas e fazer uma tokenização TeX completa seria possível, mas pode não valer o esforço, dependendo da sua contribuição real.

    • 9
  2. jubilatious1
    2025-02-07T01:41:25+08:002025-02-07T01:41:25+08:00

    Usando Raku (anteriormente conhecido como Perl_6)

    Combine o alvo desejado usando <~~>a notação regex recursiva do Raku:

    ~$ raku -ne 'put join "", m:g/ \{ ~ \}  [ <( <-[{}]>* )> || <( <-[{}]>* <~~> <-[{}]>* )> ] /;'  file.tex
    

    Exemplo de entrada:

    \SomeStyle{\otherstyle{this is the \textit{nested part} some more text...}}
    \badstyle{w}\badstyle{o}\badstyle{r}\badstyle{d}
    

    Exemplo de saída:

    \otherstyle{this is the \textit{nested part} some more text...}
    word
    

    O Raku fornece uma nova sintaxe Regex que algumas pessoas acham mais fácil de ler. O código foi tirado quase literalmente da página de documentação Regex do Raku . Aqui, simplesmente usamos m///o operador match do Raku, tornado global com o :gparâmetro nomeado:

    • O \{ ~ \} <expression>denota a sintaxe do til para estruturas aninhadas ,
    • O <-[{}]>*denota uma classe de caracteres negativos personalizada contendo qualquer caractere, exceto {}chaves. ICYMI, <+[{}]>*ou mais simplesmente <[{}]>*denotaria uma classe de caracteres positivos,
    • O <~~>denota uma regex recursiva ,
    • <(… denota marcadores )>de captura no Raku.

    Para processar um arquivo corrigindo as linhas ofensivas e exibindo as linhas não ofensivas na íntegra, use o operador ternário do Raku: Test ?? True !! False .

    ~$ raku -ne 'm:g/ \{  [ <( <-[{}]>* )> || <( <-[{}]>* <~~> <-[{}]>* )> ] \} / 
                 ?? $/.join.put 
                 !! $_.put;'   file.tex
    

    Infelizmente, no momento, todos os exemplos de código acima simplesmente retiram o - de nível superior Style(mais chaves associadas) de maneira linear, seja lá o que Stylefor. Vou trabalhar para corrigir essa falta de especificidade.

    Observadores astutos podem notar que todas as respostas acima usam m///o operador de correspondência do Raku. Para sua informação, tenho certeza de que há uma maneira de fazer isso com s///o operador de substituição do Raku (em conjunto com os marcadores de captura do Raku <() )>, mas eu queria que essas m///respostas de correspondência fossem publicadas primeiro.

    • 5
  3. meuh
    2025-02-06T23:44:54+08:002025-02-06T23:44:54+08:00

    Aqui está um sedmecanismo possível. Para simplificar, assumimos que não há caracteres sublinhados, então podemos usar um como marcador. Isso é como seu sino ascii. Inserimos o marcador no início da linha e o movemos caractere por caractere até o final da linha. Cada vez que ele se move, {adicionamos um +sinal ao início da linha para atuar como um contador. Cada vez que ele se move, }removemos a +do início. Se não tivermos mais +sinais, então equilibramos as chaves e podemos aplicar o substituto desejado, até o marcador.

    Caso a linha comece com +already, começamos adicionando !!no início e removendo no final.

    sed '
     s/^/!!_/
    :a
     /_\(.\)/{
       s//\1_/
       /{_/{
         s/^/+/
       }
       /}_/{
         /^+/!{
           s/^/mismatch{}/
           b
         }
         s///
         /^!!/b b
       }
       b a
     }
     # flow through here if _ is at eol
    :b
     # dummy t branch to clear so can detect if s done
     t c
    :c
     s/\\SomeStyle{\(.*\)}_/\1_/
     s/\\badstyle{\(.*\)}_/\1_/
     # repeat to do globally on line
     t a
     s/^!!//
     s/_$//
    '
    
    • 4
  4. Ed Morton
    2025-02-07T19:12:32+08:002025-02-07T19:12:32+08:00

    Usando qualquer awk:

    $ cat tst.awk
    {
        while ( match($0, /\\SomeStyle\{/) ) {
            head = substr($0,1,RSTART-1)
            tail = substr($0,RSTART+RLENGTH-1)
    
            gsub(/@/, "@A", tail)
            gsub(/</, "@B", tail)
            gsub(/>/, "@C", tail)
    
            while ( match(tail, /\{[^{}]*}/) ) {
                if ( RSTART == 1 ) {
                    tail = substr(tail,2,RLENGTH-2) substr(tail,RLENGTH+1)
                    gsub(/</, "{", tail)
                    gsub(/>/, "}", tail)
                    break
                }
                tail = substr(tail,1,RSTART-1) "<" substr(tail,RSTART+1,RLENGTH-2) ">" substr(tail,RSTART+RLENGTH)
            }
    
            gsub(/@C/, ">", tail)
            gsub(/@B/, "<", tail)
            gsub(/@A/, "@", tail)
    
            $0 = head tail
        }
    }
    { print }
    

    $ awk -f tst.awk file
    \otherstyle{this is the \textit{nested part} some more text...}
    

    O exemplo acima não tenta lidar com escape {ou }na entrada porque precisaria tratar \{(escaped {) de forma diferente de \\{(escaped \followed by {) e isso requer mais reflexão do que estou disposto a colocar nisso, já que não aparece na entrada de exemplo e, portanto, provavelmente não é realmente um problema para o OP e eles sempre podem fazer uma pergunta complementar se for e eles ainda não tiverem uma maneira de lidar com isso.

    Atualização: Após discussão com @StéphaneChazelas nos comentários abaixo de sua resposta , acredito que você só precisa substituir [^{}]por (\\.|[^{}\\])na expressão regular usada por match()para manipular escapes {ou }na entrada.

    Ele assume que todo \SomeStyle{ou apenas {tem um }.

    Aqui está uma versão comentada do acima, pois pode não ser óbvio à primeira vista o que está fazendo:

    {
        # Work on lines that include \SomeStyle{, and find where that starts
        # We do this in a loop in case there are multiple such strings in a line.
        while ( match($0, /\\SomeStyle\{/) ) {
            # Save the part before \SomeStyle{ as-is in "head" so we can add it back later
            head = substr($0,1,RSTART-1)
            # Save the part starting from the { in \SomeStyle{ in "tail" for further processing
            tail = substr($0,RSTART+RLENGTH-1)
    
            # Convert every @ to @A so we can then convert every < to @B and > to @C
            # so we can later change every { in the nested string to < and } to >
            # so on every loop iteration we get rid of the innermost matched { and }
            gsub(/@/, "@A", tail)
            gsub(/</, "@B", tail)
            gsub(/>/, "@C", tail)
    
            # Loop finding every innermost {...} substring, replacing the { and } with
            # < and > as we go to catch longer and longer nested substrings on each
            # iteration, stopping when RSTART is 1 because thats the start of the
            # outermost {...}. Add "print tail" after "tail = ..." to see what it is
            # doing if its not obvious.
            while ( match(tail, /\{[^{}]*}/) ) {
                if ( RSTART == 1 ) {
                    # Weve found the outermost {...} so stop looping and remove the
                    # outermost { and }
                    tail = substr(tail,2,RLENGTH-2) substr(tail,RLENGTH+1)
                    # Change all < and > chars we inserted back to their original characters
                    gsub(/</, "{", tail)
                    gsub(/>/, "}", tail)
                    break
                }
                # Change {...{foo}...} to {...<foo>...}
                tail = substr(tail,1,RSTART-1) "<" substr(tail,RSTART+1,RLENGTH-2) ">" substr(tail,RSTART+RLENGTH)
            }
    
            # Change all pre-loop replacement strings back to their original characters
            gsub(/@C/, ">", tail)
            gsub(/@B/, "<", tail)
            gsub(/@A/, "@", tail)
    
            # Reconstruct $0 from the part before \SomeStyle{ and the processed part
            $0 = head tail
        }
    }
    { print }
    
    • 4
  5. Thierry Blanc
    2025-02-07T15:44:24+08:002025-02-07T15:44:24+08:00

    O script verifica se a entrada da string de substituição está correta (sem chaves), verifica se há chaves de escape no arquivo de destino ({,}) e cria um backup.

    abordagem sed/abordagem perl:

    #!/bin/bash
    
    #
    # Remove braces statement but keep content
    #
    
    TMP="$2.bk"
    TMP1=$(mktemp) || { echo "Failed to create temp file" >&2; exit 1; }
    TMP2=$(mktemp) || { echo "Failed to create temp file" >&2; exit 1; }
    
    # Ensure cleanup of temp files on exit
    trap 'rm -f "$TMP1" "$TMP2"' EXIT
    
    if [[ $# -ne 2 ]]; then
        echo "usage: ${0##*/} '<replace>' <file>" >&2
        echo "Example: ${0##*/} '\\textemphasis' myfile.tex will replace \\textemphasis{<1>} with <1>" >&2
        exit 1
    fi
    
    # Check for braces in replacement string
    if grep -E '[{}]' <<< "$1"; then
        echo "Error: '$1' contains { or }, which is not allowed." >&2
        exit 1
    fi
    
    # Escape characters in replace string
    F=$(sed 's|\\|\\\\|g; s|{|\\{|g' <<< "$1")
    
    # Preserve escaped braces
    sed 's|\\{|\ao|g; s|\\}|\ac|g' "$2" > "$TMP1"
    
    # Backup original file
    cp -- "$2" "$TMP" || { echo "Error: Backup failed." >&2; exit 1; }
    
    # Process file
    sed '
      :a; $!{N; ba}
      s/_/_u/g
      :b
      s/'"$F"'{\([^{}]*\)}/\1/g; tb
      s/{\([^{}]*\)}/_l\1_r/g; tb
      s/_l/{/g; s/_r/}/g; s/_u/_/g' "$TMP1" > "$TMP2"
    
    # or perl 
    # perl -0777 -pe 's/'"$F"'(\{((?:(?1)|[^{}])*)\})/$2/gs' $TMP1 > $TMP2
    #
    
    # Restore escaped braces
    sed 's|\ao|\\{|g; s|\ac|\\}|g' "$TMP2" > "$2"
    
    • 2

relate perguntas

  • Grep para um conjunto de linhas de $START a $END AND que contém uma correspondência em $MIDDLE

  • Reorganize as letras e compare duas palavras

  • Subtraindo a mesma coluna entre duas linhas no awk

  • Embaralhamento de arquivo de várias linhas

  • como posso alterar o caso do caractere (de baixo para cima e vice-versa)? ao mesmo tempo [duplicado]

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Possível firmware ausente /lib/firmware/i915/* para o módulo i915

    • 3 respostas
  • Marko Smith

    Falha ao buscar o repositório de backports jessie

    • 4 respostas
  • Marko Smith

    Como exportar uma chave privada GPG e uma chave pública para um arquivo

    • 4 respostas
  • Marko Smith

    Como podemos executar um comando armazenado em uma variável?

    • 5 respostas
  • Marko Smith

    Como configurar o systemd-resolved e o systemd-networkd para usar o servidor DNS local para resolver domínios locais e o servidor DNS remoto para domínios remotos?

    • 3 respostas
  • Marko Smith

    apt-get update error no Kali Linux após a atualização do dist [duplicado]

    • 2 respostas
  • Marko Smith

    Como ver as últimas linhas x do log de serviço systemctl

    • 5 respostas
  • Marko Smith

    Nano - pule para o final do arquivo

    • 8 respostas
  • Marko Smith

    erro grub: você precisa carregar o kernel primeiro

    • 4 respostas
  • Marko Smith

    Como baixar o pacote não instalá-lo com o comando apt-get?

    • 7 respostas
  • Martin Hope
    user12345 Falha ao buscar o repositório de backports jessie 2019-03-27 04:39:28 +0800 CST
  • Martin Hope
    Carl Por que a maioria dos exemplos do systemd contém WantedBy=multi-user.target? 2019-03-15 11:49:25 +0800 CST
  • Martin Hope
    rocky Como exportar uma chave privada GPG e uma chave pública para um arquivo 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Evan Carroll status systemctl mostra: "Estado: degradado" 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim Como podemos executar um comando armazenado em uma variável? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S Por que /dev/null é um arquivo? Por que sua função não é implementada como um programa simples? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 Como ver as últimas linhas x do log de serviço systemctl 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - pule para o final do arquivo 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla Por que verdadeiro e falso são tão grandes? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis Substitua a string em um arquivo de texto enorme (70 GB), uma linha 2017-12-30 06:58:33 +0800 CST

Hot tag

linux bash debian shell-script text-processing ubuntu centos shell awk ssh

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve