Файл с разделителями трубы с пустыми записями; преобразовать в разделенную табуляцией с помощью '<empty>' между
проблема
Мне дали текстовый файл с разделителями, содержащий имена файлов и некоторую индексированную информацию из каждого файла. Моя цель - сделать этот файл разделенным табуляцией. Тем не менее, я хочу знать, где находятся пустые записи. Это будет сделано, например, с lorem||dolor
становление lorem
'\t'
<empty>
'\t'
dolor
,
Позвольте мне привести еще пару примеров того, что мне дали и что нужно:
Пример с несколькими строками: (NB. В каждой строке одинаковое количество записей.)
Дано:
||dolor|sit
amet,||adipiscing|
sed|do|eiusmod|tempor
Желаемая:
<empty> '\t' <empty> '\t' dolor '\t' sit '\n'
amet, '\t' <empty> '\t' adipiscing '\t' <empty> '\n'
sed '\t' do '\t' eiusmod '\t' tempor '\n'
Пустые записи в начале и в конце.
Дано:
|ut|labore||dolore||
Желаемая:
<empty> '\t' ut '\t' labore '/t' <empty> '\t' dolore '\t' <empty> '\t' <empty>
(Я не хочу пробелов; я просто думал, что это сделает желаемый формат более легким для чтения.)
Проблема возникает с последовательными пустыми записями. Файлы, которые мне дали, могут иметь от 1 до 36 последовательных каналов (от 0 до 37 последовательных пустых записей).
осветление
Решение не должно быть sed
, awk
, grep
, tr
и т. д. Это только те решения, на которые я смотрел. perl
или же python
сценарий (или любая другая идея, о которой я не задумывался) также приветствуется.
Мои попытки и исследования
Для попыток, которые я предпринял до и во время моего исследования, команды и их вывод включены в виде изображения 1 и текстового файла 2, чтобы не загромождать вопрос.
Ссылки на вещи, которые я посмотрел - Поиск последовательных каналов с sed
(и замена любой такой серии труб): ref. здесь; Подсчет количества пустых полей (возможно, полезно знать, сколько <empty>
нужны): ref. здесь; Самая длинная последовательность: ссылка здесь;
Системная информация
$ uname -a
CYGWIN_NT-10.0 A-1052207 2.5.2(0.297/5/3) 2016-06-23 14:29 x86_64 Cygwin
$ bash --version
GNU bash, version 4.3.42(4)-release (x86_64-unknown-cygwin) ...
$
Я использую эту версию Cygwin для Windows 10 (потому что работа требует этого)
Edit1
Мне было неясно, что именно было желательно.
Вот короткий пример, показывающий, что я хотел бы с трубами в начале и в конце:
(Это то, что вы увидите, и вам нужно будет набрать, если вы наберете первую строку, нажмете ввод, наберете вторую строку, нажмете ввод и т. Д. Его нельзя скопировать / вставить, потому что >
появляется только после того, как вы нажали Enter на предыдущей строке.)
$ cat > myfile.txt<<EOF
> ||foo|||bar||
> EOF
$ <**command-to-be-used**> myfile.txt | cat -A
<empty>^I<empty>^Ifoo^I<empty>^I<empty>^Ibar^I<empty>^I<empty>$
Где ^I
как моя версия bash
показывает '\t'
, Из ответов, приведенных с использованием примера текста, который я дал, я понял, что хотел бы <empty>
в конце, после labore
(см. команду ниже). Обратите внимание, что полученные ответы (спасибо @Neil_McGuigan и @Ed_Morton) действительно дают '\t'
после labore
просто не <empty>
, Это моя вина, так как я не был достаточно ясен в своем первоначальном описании. Мои извенения.
Я смог достичь своей цели с помощью небольшого изменения команды @ Neil_McGuigan. Обратите внимание, что если вы хотите набрать этот "построчно", как показано, вам нужно будет вставить пробел и \
в конце каждой строки.
$ echo "||lorem|ipsum||sit|amet,||||eiusmod|tempor|||labore|" |
awk '
{
$1=$1; n_empty=0;
for(i=1; i<=NF; i++)
{
if($i=="") {$i="<empty>"; n_empty++;}
};
print
}
END {print n_empty" entries are empty" | "cat 1>&2";}
' FS='|' OFS=$'\t'
| cat -A
дает результат:
<empty>^I<empty>^Ilorem^Iipsum^I<empty>^Isit^Iamet,^I<empty>^I<empty>^I<empty>^Ieiusmod^Itempor^I<empty>^I<empty>^Ilabore^I<empty>$
9 entries are empty
Еще раз, для тех, кто не хочет прокручивать, этот вывод выглядит следующим образом:
<empty>^I<empty>^Ilorem^Iipsum^I<empty>^Isit^Iamet,^I<empty>^I<empty>^I<empty>^Ieiusmod^Itempor^I<empty>^I<empty>^Ilabore^I<empty>$
9 entries are empty
(Обратите внимание, что количество пустых записей записывается в stderr
не было необходимости, но это приятно.)
Извините за то, что не ясно о том, что я хотел.
Что я использовал успешно
Благодаря @Neil_McGuigan и @Ed_Morton я смог найти решение, которое искал. Моя последняя команда была следующей:
$ awk '{$1=$1; n_empty=0; for(i=1; i<=NF; i++) {if($i=="") {$i="<empty>"; n_empty++;}}; print;} END {print n_empty" entries are empty" | "cat 1>&2";}' FS='|' OFS=$'\t' file_pipe-delim.txt > file_tab-delim.txt
$
На тот случай, если вы не хотите прокручивать, вот та же команда:
$ awk '{$1=$1; for(i=1; i<NF; i++){ if($(i)=="")$(i)="<empty>" }; print}'
FS='|' OFS=$'\t' file_pipe-delim.txt | sed 's/\t$/\t<empty>/g' >
file_tab-delim.txt
$
Вот пример, где файл сделан, преобразован и сохранен:
(Это то, что вы увидите, и вам нужно будет набрать, если вы наберете первую строку, нажмете ввод, наберете вторую строку, нажмете ввод и т. Д. Его нельзя скопировать / вставить, потому что >
появляется только после того, как вы нажали Enter на предыдущей строке.)
$ cat > file_pipe-delim.txt<<EOF
> ||dolor|sit
> amet,||adipiscing|
> sed|do|eiusmod|tempor
> |||
> |aliqua.|Ut|
> EOF
$ awk '{$1=$1; n_empty=0; for(i=1; i<=NF; i++)
{if($i=="") {$i="<empty>"; n_empty++;}}; print;} END
{print n_empty" entries are empty" | "cat 1>&2";}'
FS='|' OFS=$'\t' file_pipe-delim.txt > file_tab-delim.txt
$ cat -A file_tab-delim.txt
<empty>^I<empty>^Idolor^Isit$
amet,^I<empty>^Iadipiscing^I<empty>$
sed^Ido^Ieiusmod^Itempor$
<empty>^I<empty>^I<empty>^I<empty>$
<empty>^Ialiqua.^IUt^I<empty>$
$
Наконец, давайте вернем строку, которая доставила мне неприятности. Мы можем получить желаемый результат следующим образом:
$ echo "||lorem|ipsum||sit|amet,||||eiusmod|tempor|||labore|" | awk '{$1=$1; n_empty=0; for(i=1; i<=NF; i++) {if($i=="") {$i="<empty>"; n_empty++;}}; print;} END {print n_empty" entries are empty" | "cat 1>&2";}' FS='|' OFS=$'\t' | cat -A
<empty>^I<empty>^Ilorem^Iipsum^I<empty>^Isit^Iamet,^I<empty>^I<empty>^I<empty>^Ieiusmod^Itempor^I<empty>^I<empty>^Ilabore^I<empty>$
9 entries are empty
Теперь та же команда без трубы cat -A
Это означает, что мы не увидим ^I
для каждого '\t'
; мы просто увидим текст в виде вкладок.
$ echo "||lorem|ipsum||sit|amet,||||eiusmod|tempor|||labore|" | \
awk '{$1=$1; n_empty=0; for(i=1; i<=NF; i++) \
{if($i=="") {$i="<empty>"; n_empty++;}}; print;} END \
{print n_empty" entries are empty" | "cat 1>&2";}' \
FS='|' OFS=$'\t'
<empty> <empty> lorem ipsum <empty> sit amet, <empty> <empty> <empty>eiusmod tempor <empty> <empty> labore <empty>
9 entries are empty
2 ответа
awk '
{
$1=$1;
for(i=1; i<NF; i++) {
if($i=="") { $i="<empty>"; empty++ }
};
print
}
END { print empty" empty" | "cat 1>&2"; }
' FS='|' OFS=$'\t'
Должен сделать свое дело. $1=$1 говорит awk "перестроить" поля ввода, чтобы их можно было использовать с новым OutputFieldSeparator (OFS).
print empty" empty" | "cat 1>&2"
выводит "n empty" в stderr. Вы можете опустить его, если хотите
Вам нужно только сделать ||
-> |<empty>|
подстановка дважды, независимо от того, сколько раз этот шаблон появляется, если вы делаете это глобально каждый раз:
$ sed 's/||/|<empty>|/g; s/||/|<empty>|/g; s/|/\t/g' file
lorem ipsum <empty> sit amet, <empty> <empty> <empty> eiusmod tempor <empty> <empty> labore
или если вы предпочитаете awk:
$ awk '{while(gsub(/\|\|/,"|<empty>|")); gsub(/\|/,"\t")} 1' file
lorem ipsum <empty> sit amet, <empty> <empty> <empty> eiusmod tempor <empty> <empty> labore
С некоторыми побегами вам может понадобиться '$'\t''
вместо просто \t
,