Преобразование длинных чисел в hex с sed

Я пытаюсь сделать команду sed чтобы числа, превышающие 3 цифры, были преобразованы в hex. Т.е. строка типа 124 3275 7535 должна приводить к 124 0xccb 0x1d6f . Вот что я сейчас имею:

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

Но когда строка не соответствует, она пытается запустить неизмененную строку в качестве внешней команды, поэтому для строки примера выше я получаю

sh: 1: 124: не найдено

Как я могу достичь того, что я пытаюсь сделать (желательно все еще использовать sed )?

Хотя это не «с sed» по вашему названию вопроса, если вы переключитесь с sed на perl, вы можете использовать эквивалентное выражение, такое как

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

что должно позволить вам сохранить другие выражения в вашей цепочке более или менее, как есть.

Флаг e команды s реализации sed для sed GNU предназначен для оценки содержимого пространства шаблонов после того, как подстановка была применена (успешно), и заменила пространство шаблонов на его вывод, а не оценивала замену .

Здесь для ввода типа:

 foo 1234 123 

Вам потребуется подзадача, чтобы получить пространство шаблонов, содержащее:

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

Для того чтобы флаг e преобразовал это в foo 0x3d2 123 через команду оболочки. Это не невозможно, как с:

 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" 

Но это довольно неудобно и означает запуск одной оболочки на соответствующую строку ввода. Даже не используя этот GNUism, вы также можете:

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

Который будет запускать один sh .

Кроме того, оценка произвольных данных как кода оболочки, как это, имеет тенденцию заставлять меня нервничать. Например, без LC_ALL = C выше, это будет представлять собой произвольную уязвимость выполнения команд. Попробуйте, например, что-то вроде вывода:

 printf '0000\200; echo GOTCHA>&2\n' 

в локали UTF-8.

Здесь вы предпочитаете использовать что-то вроде perl :

 perl -pe 's/\d{4,}/sprintf "0x%x", $&/ge' 

Флаг perl 's e больше соответствует тому, что вы ожидаете. Это делает переопределение в качестве perl кода (и не запускает новый интерпретатор perl каждый раз, как с помощью GNU sed e ).

awk был разработан для такого типа манипуляции с широким спектром текста. Обратите внимание: нет необходимости подключать любые вспомогательные инструменты.

 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' 

выход по вашему образцу:

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

Это не так плохо. Ты просто должен помнить, для чего он нужен.

Однако здесь есть несколько недостатков. Например, если в этой входной строке есть квадратные скобки, вся вещь идет вбок. У меня есть пара строк, чтобы справиться с этим, но я не знаю, хочу ли я его выкопать прямо сейчас.

Другое дело, как написано, будет делать шестнадцатеричные числа, за которыми следует \n ewline. Портативно это единственный способ пойти w / dc . W / GNU dc вы можете заменить &p w / &n .

Так или иначе:

вывод

 124 CCB 1D6F 

С GNU n :

вывод

 124 CCB 1D6F 

Я думаю, мы могли бы сделать 0x вещь, если вы хотите:

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

… который предполагает использование инструментов GNU.

вывод

 124 0xCCB 0x1D6F 

Что бы ни говорили о других инструментах в вашем ящике, sed был разработан, чтобы превратить поток во что-то полезное. Это не очень хорошо w / numbers, и это факт, но довольно хорошо передать их тем инструментам, которые являются, например, калькуляторами.

Есть и другие варианты с GNU sed – хотя никто не отрывается как просто, и я ожидаю, что вам будет трудно найти решение, которое, впрочем, имеет значение. Что-то вроде вашего кода в вопросе:

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

вывод

 124 0xccb 0x1d6f 

Я второй, что сказал Peter.O в комментариях: вот способ bash сделать это (ему нужно в конце каждого номера):

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

Если в вашем потоке ввода нет в конце строки (как это выглядит из вашего примера), чем sed пригодится:

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