Файл с разделителями трубы с пустыми записями; преобразовать в разделенную табуляцией с помощью '<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,

Другие вопросы по тегам