Заменить каждое вхождение символа, кроме последнего, в каждой строке

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

Однако в каждой из этих ситуаций эти пары ключей -значений могут быть переданы в программу с помощью других средств, а не только переменных среды. Например, в Ant вы можете использовать «-файл свойств (имя файла )» для передачи отформатированного набора значений ключей -в файле свойств. Confd позволяет использовать «-бэкэнд-файл -файл (файл yaml )».

Я передал переменные окружения в виде "C __любое _значение='my.property.key=значение'". Затем я переключил вызов программы, чтобы сначала сгенерировать файл :

.
set | awk -- 'BEGIN { FS="'\''" } /^C__/ {print $2}' > my-key-values.txt

Команда setв оболочке Borne выводит каждое свойство в отдельной строке в виде

C__any_value='my.property.key=the value'

Команда awkобработает только переменные среды, начинающиеся с C__, а затем извлечет значения, заключенные в одинарные кавычки.

Этот метод требует, чтобы значение переменной среды было установлено в точной форме, которая требуется программе обработки. Кроме того, если значение или ключ вашего свойства будут содержать одинарные кавычки, вам нужно будет изменить символ-разделитель полей awk на что-то, что, как вы знаете, не будет отображаться, и окружить значение этим символом.Например, чтобы использовать %в качестве разделителя:

$ C__1="%my.key=the'value%"
$ set | awk -- 'BEGIN { FS="%" } /^C__/ {print $2}'
my.key=the'"'"'value

(точный вывод будет зависеть от вашей оболочки. )Вам потребуется предпринять дополнительные шаги, чтобы расшифровать экранирование цитаты.

15
17.05.2020, 22:12
6 ответов

Вот несколько альтернатив, которые вы можете использовать.

$ sed -e '
   s/|[^|]*$/\n&/
   s/\n|/\n/
   y/\n|/| /
' file

$ perl -pe 's/\|/ / until tr/|/|/ == 1' file

$ perl -pe 'my $k=tr/|/|/; s/\|/ / while $k-->1' file
2
28.04.2021, 23:14

Без петли:

sed 's/\(.*\)|/\1\
/; s/|/ /g; s/\
/|/'
  • Заменить последнюю вертикальную черту в каждой строке новой строкой. (Это может быть вставлено в пространство шаблона, но гарантированно не будет там изначально.)
  • Замените все оставшиеся вертикальные черты пробелами.
  • Заменить новую строку (, вставленную первой командой )с вертикальной полосой.
1
28.04.2021, 23:14

Подход, отличный от явного цикла Квазимодо в sed:

.
$ sed 'h; s/.*|//; x; s/|[^|]*$//; y/|/ /; G; y/\n/|/' file
FLD1      SFK TK  FLD2    FLD4  FLD5  -           20200515  NNNN |406   RCO 301
FLD1      SFK TK  FLD2    FLD4  FLD5  -           20200515  NNNN |0
FLD1      SFK TK  FLD2    FLD4  FLD5  -           20200515  NNNN |0

Для каждой строки это сохраняет строку в ячейке удержания с помощью h, а затем удаляет все в строке до последней строки |включительно. Затем он заменяет исходную копию строки и удаляет последнюю |и все после нее.

Пространство шаблона теперь содержит исходную первую часть строки, а пространство хранения содержит последнюю часть строки.

Первая команда y///заменяет все остальные |пробелами. Gдобавляет пробел в конец пространства шаблона с символом новой строки в -между ними. Вторая команда y///преобразует этот символ новой строки в |, и все готово.

Выполнение ограниченного (фиксированного )числа s///замен и использование более быстрой команды y///, когда это возможно, означает, что это выполняется быстрее, чем явное изменение цикла (~2,3 секунды для данных объемом 50 МБ, по сравнению до ~7,8 секунды для тех же данных с циклом, используя GNU sedна моей машине ).

Интересно, что использование обратной ссылки в явном варианте цикла, как это сделали и я, и Исаак, замедляет его еще больше (~33 с с вариантом Исаака и ~29 с с моим(в комментариях ), на том же наборе данных и при тех же условиях, что и выше ).


Используя awk, этот почти заменяет все |разделители, кроме последнего, пробелом. «Почти», так как вставляет пробел перед последним |.

$ awk -F '|' 'BEGIN { OFS = " " } { $NF = "|" $NF; print }' file
FLD1      SFK TK  FLD2    FLD4  FLD5  -           20200515  NNNN  |406   RCO 301
FLD1      SFK TK  FLD2    FLD4  FLD5  -           20200515  NNNN  |0
FLD1      SFK TK  FLD2    FLD4  FLD5  -           20200515  NNNN  |0

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

С учетом поведения awkпо умолчанию (пробел является разделителем поля вывода по умолчанию, а разделитель поля ввода доступен какFS):

awk -F '|' '{ $NF = FS $NF; print }' file

или, немного короче, любезно предоставлено @Isaac,

awk -F '|' '{ $NF = FS $NF }; 1' file
11
28.04.2021, 23:14
sed ':a;/[|].*[|]/s/[|]/ /;ta' file
  • /[|].*[|]/:Если линия состоит из двух труб,
  • s/[|]/ /:Замените первое пробелом.
  • ta:Если была произведена замена, вернитесь к :a.

Выход:

$ sed ':a;/[|].*[|]/s/[|]/ /;ta' file
FLD1      SFK TK  FLD2    FLD4  FLD5  -           20200515  NNNN |406   RCO 301
FLD1      SFK TK  FLD2    FLD4  FLD5  -           20200515  NNNN |0
FLD1      SFK TK  FLD2    FLD4  FLD5  -           20200515  NNNN |0

Как заметил @steeldriver, вы можете использовать просто |вместо [|]в базовом регулярном выражении (BRE ), как в случае выше. Если вы добавите флаг -Eв sed, расширенное регулярное выражение (ERE )будет включено, и тогда вам нужно будет написать [|]или \|.


Для полноты картиныСпецификация POSIX sed говорит, что «Команды редактирования, отличные от {...}, a, b, c, i, r, t, w, :, и#может сопровождаться точкой с запятой". Тогда совместимой альтернативой вышеизложенному является:

sed -e ':a' -e '/[|].*[|]/s/[|]/ /;t a' file
20
28.04.2021, 23:14

Письмо:

sed -e ':a' -e '/|\(.*|\)/s// \1/;ta' file
  • -e ':a'определить метку(a)для перехода.
  • -e 'запустить другой раздел скрипта.
  • /|\(.*|\)/одно регулярное выражение соответствует двум |, все посередине и захватить все промежуточное и последнее |.
  • s// \1/заменить все совпадающее выше тем, что было захвачено.
  • ;taповторите цикл.
  • ' fileдля указанного имени файла.

Для измерения скорости всех вариантов (от более быстрого к более медленному )вы можете использовать:

#! /bin/bash
TIMEFORMAT='run : %lR sec'

read -d '' str <<\END
FLD1     |SFK TK |FLD2   |FLD4 |FLD5 |-          |20200515 |NNNN |406   RCO 301
FLD1     |SFK TK |FLD2   |FLD4 |FLD5 |-          |20200515 |NNNN |0
FLD1     |SFK TK |FLD2   |FLD4 |FLD5 |-          |20200515 |NNNN |0'
END

n=${1:-100}; printf "$str"'%.0s\n' $(seq "$n") > file

time perl -pe 's/\|(?=.*\|)/ /g' file >/dev/null
time sed -E ':a;/\|.{1,}\|/s/\|/ /1;ta' file >/dev/null
time sed 'h; s/.*|//; x; s/|[^|]*$//; y/|/ /; G; y/\n/|/' file >/dev/null
time sed 's/\(.*\)|/\1\x00/;y/|/ /;s/\x00/|/1' file >/dev/null
time sed 's/\(.*\)|/\1\n/;y/|/ /;s/\n/|/1' file >/dev/null

Используется как:

$./testbash.sh 235000
run    : 0m07.676s sec
run    : 0m17.753s sec
run    : 0m22.074s sec
run    : 0m24.036s sec
run    : 0m24.047s sec
5
28.04.2021, 23:14

Используя Perl, вы можете запустить что-то вроде

perl -pe 's/\|(?=.*\|)/ /g'     ex

где:

  • perl -peдействие --выполняет действие и печатает
  • \|(?=.*\|)— это регулярное выражение, которое сопоставляет |с неиспользованным просмотром (?=.*|), содержащим другое|
9
28.04.2021, 23:14

Теги

Похожие вопросы