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 / 793991
Accepted
Ricardo Albear
Ricardo Albear
Asked: 2025-04-22 00:48:33 +0800 CST2025-04-22 00:48:33 +0800 CST 2025-04-22 00:48:33 +0800 CST

sed no makefile não está funcionando como esperado ao usar regex

  • 772

Tenho um makefile com este comando que converte nomes de pastas ./cmd/de snake_caseparaPascalCase

test:
    @for f in $(shell ls ./cmd/); do \
        echo $${f}; \
        echo $${f} | sed -r 's/(^|_)([a-z])/\U\2/g'; \
     done

O que obtenho quando o executo é, com uma letra maiúscula prefixada U:

api_get_manual
UapiUgetUmanual

E o que espero obter:

ApiGetManual
sed
  • 2 2 respostas
  • 379 Views

2 respostas

  • Voted
  1. Best Answer
    Stéphane Chazelas
    2025-04-22T02:19:55+08:002025-04-22T02:19:55+08:00

    \U, como -r(para o qual -Eagora é o equivalente padrão) é uma extensão não padrão da implementação GNU de sed, inspirada em ex/ vi, também encontrada em perl, não encontrada em muitas outras implementações.

    Aqui, em vez disso, você poderia fazer:

    SHELL = zsh
    test:
            @for f (cmd/*(N:t)) print -rl -- $$f $${$${(C)f}//_}
    

    Usando:

    • cmd/*(N:t)para expandir o glob de Nforma ullglob, obtendo o tail de cada expansão.
    • ${(C)var}para colocar palavras em maiúscula na variável
    • ${var//_}à la ksh para remover _caracteres posteriormente
    • print -rl --para imprimir raw em llinhas separadas.

    Observe que os nomes dos arquivos são decodificados em texto e convertidos para letras maiúsculas de acordo com a localidade do usuário (categoria LC_CTYPE).

    O exemplo acima, para cada sequência de um ou mais caracteres alfanuméricos, converte o primeiro caractere para maiúsculas e todos os demais para minúsculas, além de remover todos os sublinhados.

    Uma abordagem mais próxima da sua é remover apenas os sublinhados seguidos por uma letra minúscula (e converter somente essa letra para maiúscula, deixando o resto intacto):

    SHELL = zsh
    test:
            @set -o extendedglob; for f (cmd/*(N:t)) \
              print -rl -- $$f $${f//(#b)((#s)|_)([[:lower:]])/$$match[2]:u}
    

    Onde

    • (#b)é ativar referências posteriores, para que grupos de captura possam ser referenciados na $matchmatriz na substituição
    • (#s)para storta, o equivalente a regex^
    • [[:lower:]]corresponde a caracteres classificados como minúsculos, como em expressões regulares. [a-z]para restringir àqueles entre ae zque em zshé feito com base no valor do ponto de código, portanto limitado a abcdefghijklmnopqrstuvwxyz
    • $var:upara converter para maiúsculas como em csh, respeitando o idioma.

    Sem zsh:

    test:
            @CDPATH= cd cmd && \
              perl -le 'for (<*>) {print; s/[[:alnum:]]+/\u\L$$&/g; s/_//g; print}'
    

    Assume apenas letras ASCII ( stéphaneseria alterado para, StéPhanepor exemplo, pois énão é reconhecido como uma letra).

    Ou como na sua abordagem:

    test:
            @CDPATH= cd cmd && \
              perl -le 'for (<*>) {print; s/(^|_)([a-z])/\U$$2/g; print}'
    

    Se limitado a utilitários POSIX, você pode usar awkpara fazer a capitalização:

    test:
            @CDPATH= cd cmd && awk -- '                              \
              BEGIN {for (i = 1; i < ARGC; i++) {                    \
                arg = ARGV[i]; out = "";                             \
                print arg;                                           \
                while (match(arg, /[[:alnum:]]+/)) {                 \
                  out = out                                          \
                        substr(arg, 1, RSTART - 1)                   \
                        toupper(substr(arg, RSTART, 1))              \
                        tolower(substr(arg, RSTART+1, RLENGTH - 1)); \
                  arg = substr(arg, RSTART+RLENGTH)};                \
                  out = out arg;                                     \
                  gsub("_", "", out);                                \
                  print out                                          \
                }                                                    \
              }' *
    

    Assim como o zsh, ele respeitará o idioma para decodificar nomes de arquivos como texto, classificando caracteres alnume convertendo-os para letras maiúsculas.

    Para combinar com sua abordagem:

    test:
            @CDPATH= cd cmd && awk -- '                                   \
              BEGIN {for (i = 1; i < ARGC; i++) {                         \
                arg = ARGV[i]; out = ""; x = 0;                           \
                print arg;                                                \
                while (match(arg, (x++ ? "_" : "(^|_)") "[[:lower:]]")) { \
                  out = out                                               \
                        substr(arg, 1, RSTART-1)                          \
                        toupper(substr(arg, RSTART+RLENGTH-1, 1));        \
                  arg = substr(arg, RSTART+RLENGTH)};                     \
                  out = out arg;                                          \
                  gsub("_", "", out);                                     \
                  print out                                               \
                }                                                         \
              }' *
    

    Algumas outras notas:

    • Seu $(shell ...)é expandido por makepara o código que foi passado sem qualquer tipo de sanitização, portanto, não funcionará para nomes de arquivo que contenham caracteres especiais na sintaxe do shell, como espaço, ;, *, 'etc. Na verdade, esse é um caso típico de vulnerabilidade de execução arbitrária de código. Mas, por outro lado, ao usar, makevocê precisa desistir e esperar fazer qualquer coisa com segurança ou confiabilidade. Ele só deve ser usado com dados estritamente controlados (aqui pode ser adequado se você puder garantir que o cmddiretório conterá apenas os arquivos que você espera).
    • echonão pode ser usado para dados arbitrários
    • em shells diferentes de zsh, incluindo sho shell padrão para make, expansões de parâmetros devem ser colocadas entre aspas para evitar split+glob, então $${f}deve ser assim "$$f"(ou "$${f}"se preferir).
    • 6
  2. Ed Morton
    2025-04-22T18:34:31+08:002025-04-22T18:34:31+08:00

    Seu comentário nos diz que:

    1. Você não está usando o GNU sed, que é necessário para \U.
    2. Seu problema não tem nada a ver com chamar sed de um Makefile, já que você obtém o mesmo comportamento apenas chamando sed diretamente na linha de comando.

    Em vez de depender do GNU sed, você pode fazer isso usando qualquer awk em qualquer shell em qualquer máquina Unix:

    $ echo 'api_get_manual' |
    awk '{
        r = "_" $0
        while ( match(r, /_[a-z]/) ) {
            r = substr(r,1,RSTART-1) toupper(substr(r,RSTART+1,1)) substr(r,RSTART+RLENGTH)
        }
        sub(/^_/, "", r)
        print r
    }'
    Api_Get_Manual
    

    Aqui está o exemplo acima sendo executado em alguma entrada que não é abordada no exemplo da pergunta, para que você possa decidir se a saída é desejável ou não:

    $ cat file
    api_get_manual
    this_7
    _That
    foo:bar
    foo.pdf
    bar.c
    

    awk '{
        r = "_" $0
        while ( match(r, /_[a-z]/) ) {
            r = substr(r,1,RSTART-1) toupper(substr(r,RSTART+1,1)) substr(r,RSTART+RLENGTH)
        }
        sub(/^_/, "", r)
        print $0 "\t-> " r
    }' file
    api_get_manual  -> ApiGetManual
    this_7  -> This_7
    _That   -> _That
    foo:bar -> Foo:bar
    foo.pdf -> Foo.pdf
    bar.c   -> Bar.c
    

    Para usar qualquer um dos itens acima em um Makefile $0é preciso tornar-se $$0e o script awk tem que logicamente estar todos em uma linha, então você precisa adicionar alguns ;s e escapar as novas linhas dentro do script, por exemplo (não testado):

    awk '{                                      \
        r = "_" $$0;                            \
        while ( match(r, /_[a-z]/) ) {          \
            r = substr(r,1,RSTART-1) toupper(substr(r,RSTART+1,1)) substr(r,RSTART+RLENGTH) \
        }                                       \
        sub(/^_/, "", r);                       \
        print r                                 \
    }'
    
    • 2

relate perguntas

  • Linux grep o que no arquivo 1 está no arquivo 2 [duplicado]

  • como grep linhas após a terceira vírgula com condição

  • remova o número de linhas duplicadas com base na correspondência antes da primeira vírgula

  • Como posso melhorar este script de conversão de personagens?

  • Como remover uma única linha entre duas linhas

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