Convertendo números longos para hexadecimais com sed

5

Estou tentando criar um comando sed para que os números com mais de 3 dígitos sejam convertidos em hexadecimais. Ou seja uma string como 124 3275 7535 deve resultar em 124 0xccb 0x1d6f . Aqui está o que eu tenho atualmente:

sed 's@\([0-9]\{4,\}\)@sh -c "printf 0x%x "@ge'

Mas quando a string não combina, ela tenta executar a string inalterada como um comando externo, então, para a string de exemplo acima, obtenho

sh: 1: 124: not found

Como posso conseguir o que estou tentando fazer (de preferência ainda usando sed )?

    
por Ruslan 25.06.2015 / 12:30

5 respostas

5

Embora não seja "com sed" de acordo com o título da pergunta, se você alternar de sed para perl, poderá usar uma expressão equivalente, como

perl -p -e 's/\b\d{4,}\b/sprintf "%#x", $&/ge'

que deve permitir-lhe preservar outras expressões na sua cadeia mais ou menos como é.

    
por 25.06.2015 / 14:00
6

O e do comando s da implementação GNU de sed é para avaliar o conteúdo do espaço padrão após a substituição ter sido aplicada (com sucesso), e substituir o espaço do padrão com sua saída, não para avaliar a substituição .

Aqui, para uma entrada como:

foo 1234 123

Você precisaria da substituição para resultar no espaço padrão contendo:

printf %s 'foo '
printf 0x%x 1234
printf %s ' 123'

Para o sinalizador e converter isso em foo 0x3d2 123 via o comando shell. Isso não é impossível como com:

LC_ALL=C sed -E "
  /[0-9]{4}/!b # optimisation
  s/'/&\\&/g
  s/[0-9]{4,}/'\nprintf 0x%x &\nprintf %s '/g
  s/.*/printf %s '&'/e"

Mas isso é bastante complicado e significa executar um shell por linha de entrada correspondente. Sem mesmo usar esse GNU, você poderia fazer:

LC_ALL=C sed "
  s/'/&\\&/g
  s/[0-9]\{4,\}/'\
printf 0x%x &\
printf %s '/g
  s/.*/printf %s '&\
'/" | sh

Qual seria um sh .

Além disso, avaliar dados arbitrários como códigos de shell como esse tendem a me deixar nervoso. Por exemplo, sem o LC_ALL = C acima, isso constituiria uma vulnerabilidade de execução de comando arbitrária. Tente, por exemplo, algo como a saída de:

printf '00000; echo GOTCHA>&2\n'

em uma localidade UTF-8.

Aqui, você prefere usar algo como perl :

perl -pe 's/\d{4,}/sprintf "0x%x", $&/ge'
<%>perl e % bandeira é mais inline com o que você espera. Ele avalia a subsituição como perl code (e não está lançando um novo interpretador perl sempre como no GNU sed ' e ).

    
por 25.06.2015 / 14:10
4

awk foi projetado para exatamente esse tipo de manipulação de texto de amplo espectro. Note que não há necessidade de canalizar para nenhuma ferramenta secundária.

awk '{ for( fn=1;fn<=NF;fn++ ){
           fmat=(length($fn)>3)?"0x%x":"%s"
           dlim=(fn==NF?"\n":" ")  
           printf( fmat dlim, $fn )}}' <<<'124 3275 7535' 

saída, conforme sua amostra:

124 0xccb 0x1d6f
    
por 25.06.2015 / 13:32
3
echo 124 3275 7535 |
sed 's/.*/[&]p/;s/[0-9]\{4,\}/]P&p[/g;1s/^/16o /' |dc

Não é tão ruim nisso. Você só tem que lembrar o que é.

Existem algumas desvantagens aqui, no entanto. Por exemplo, se houver colchetes nessa cadeia de entrada, a coisa toda vai para os lados. Eu tenho algumas linhas para lidar com isso, mas eu não sei se quero desenterrá-lo agora.

A outra coisa é, como está escrito, que fará os números hexadecimais cada um seguido por um \n ewline. Portably é a única maneira de ir com dc . W / GNU dc você pode substituir o &p w / &n .

De qualquer forma:

saída

124 CCB
 1D6F

Com o n do GNU:

saída

124 CCB 1D6F

Acho que poderíamos fazer a 0x , se você quiser:

echo 124 3275 7535|
sed -Ee1i16o -e's/.*/[&]p/;s/[0-9]{4,}/0x]P&n[/g'|dc

... que assume ferramentas GNU em todos os lugares.

saída

124 0xCCB 0x1D6F

O que quer que seja dito sobre as outras ferramentas na sua caixa, sed foi projetado para transformar um fluxo em algo útil. Não é muito bom com números, e isso é um fato, mas é muito bom transferi-los para as ferramentas que são, como calculadoras.

Existem outras opções com o GNU sed - embora nenhuma seja simples, e espero que você tenha dificuldade em encontrar uma solução que o faça. Algo como o seu código na pergunta:

echo 124 3275 7535|
sed -E "h;s/[0-9]{4,}/%#x/g;s/[0-9]+/%d/g
        s/.*/printf '&' \\/;G;e"

saída

124 0xccb 0x1d6f
    
por 25.06.2015 / 13:38
0

Eu segundo o que Peter.O disse nos comentários: aqui está uma maneira de bash de fazer isso (ele precisa de um no final de cada número):

echo '124 3275 7535 ' | while read -d ' ' x; do [ ${#x} -ge 4 ] && printf "0x%x " $x || printf "%d " $x; done

Se o fluxo de entrada não tiver um no final da linha (como parece no exemplo), então sed será útil:

echo '124 3275 7535' | sed 's/$/ /' | while read -d ' ' x; do [ ${#x} -ge 4 ] && printf "0x%x " $x || printf "%d " $x; done
    
por 25.06.2015 / 13:24

Tags