Использование обеих утилит GNU Utils с Mac Utils в bash

Я работаю с построением очень больших файлов с N количеством соответствующих записей данных. (N зависит от файла).

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

К сожалению, я использую MacOSx, где у меня возникают проблемы при попытке удалить последнюю строку файла. Я читал, что наиболее эффективным способом было использование команд head/tail bash для вырезания разделов данных. Поскольку head -n -1 не работает для MacOSx, мне пришлось установить coreutils через homebrew, где команда ghead прекрасно работает. Однако команда,

tail -n+9 $COUNTER/test.csv | ghead -n -1 $COUNTER/test.csv  >> gfinal.csv

не работает. Менее чем приятный обходной путь заключался в том, что мне пришлось разделить команды, использовать ghead > newfile, а затем использовать tail для newfile > gfinal. К сожалению, это займет некоторое время, так как я должен написать новый файл с первой ghead.

Есть ли обходной путь для объединения обеих утилит GNU со стандартными утилитами Mac?

Спасибо, Кевен


person keven ren    schedule 12.11.2015    source источник


Ответы (2)


Проблема с вашей командой заключается в том, что вы указываете файловый операнд снова для команды ghead вместо того, чтобы позволить ей принимать данные из stdin через конвейер; это приводит к тому, что ghead игнорирует ввод stdin, поэтому первый сегмент конвейера фактически игнорируется; просто опустите файловый операнд для команды ghead:

tail -n+9 "$COUNTER/test.csv" | ghead -n -1 >> gfinal.csv

Тем не менее, если вы хотите удалить только последнюю строку, нет необходимости в GNU head — подойдет собственный BSD sed OS X:

tail -n +9 "$COUNTER/test.csv" | sed '$d' >> gfinal.csv

$ соответствует последней строке, а d удаляет ее (это означает, что она не будет выводиться).

Наконец, как указывает @ghoti в комментарии, вы можете сделать это all, используя sed:

sed -n '9,$ {$!p;}' file

Опция -n указывает sed производить вывод только при явном запросе; 9,$ соответствует всему от строки 9 до (,) конца файла (последняя строка, $), а {$!p;} печатает (p) каждую строку в этом диапазоне, кроме (!) последней ($).

person mklement0    schedule 12.11.2015
comment
@ghoti: Отличное замечание, спасибо; Я обновил ответ, хотя выбрал вариант, который, на мой взгляд, лучше выражает намерение. - person mklement0; 12.11.2015
comment
Ах, я удалил свой комментарий, чтобы превратить его в ответ. :) Ваш новый сценарий sed выражает цель ОП с большей поэзией, но я не думаю, что он делает это более четко. Таким образом, он говорит печатать строки, которые соответствуют этим критериям, а не просто удалять эти диапазоны строк из потока. Я бы предположил, что это просто разные взгляды на проблему. (Но +1 за ваше отличное объяснение, как обычно.) - person ghoti; 12.11.2015
comment
@ghoti: Спасибо; точка взята с разных точек зрения. Однако я скажу, что моя команда sed больше похожа на подход OP. - person mklement0; 12.11.2015
comment
Привет, спасибо за оба ваших ответа! Если бы я не использовал MacOSx, мне было бы разрешено использовать tail | глава. Почему это другой случай для ghead? Кроме того, в случае, когда файлы имеют ~ 1 миллион целых, есть ли способ удалить последнюю строку без фактического чтения данных? Из того, что я читал в других сообщениях stackexchange, которые возглавляют | хвостовой метод является наиболее эффективным. - person keven ren; 12.11.2015
comment
@kevenren: вам просто нужно удалить аргумент $COUNTER/test.csv из команды ghead, чтобы вся ваша команда работала (это то, что я пытался объяснить в начале своего поста). - person mklement0; 12.11.2015
comment
Свят, я только что понял ошибку. Спасибо! Я целую вечность смотрел на код, задаваясь вопросом, почему он не работает! (такая досадная ошибка новичка) - person keven ren; 12.11.2015
comment
@kevenren: Не беспокойтесь - я тоже этого не видел, когда впервые ответил. Что касается эффективности: возможной оптимизацией будет то, что ghead начнет чтение с конца файла с отрицательным аргументом номера строки, но это имеет смысл только в том случае, если число попадает в нижний половина файла - и без чтения всего файла и подсчета строк вы не можете знать, так ли это, поэтому эта оптимизация не вариант (за исключением того, если вы предполагаете, что для младших чисел конец лучше отправная точка с эвристически определенным порогом). Как на самом деле действует ghead, я не знаю. - person mklement0; 12.11.2015

Я понимаю, что ваш вопрос касается использования head и tail, но я отвечу так, как будто вы заинтересованы в решении исходной проблемы, а не в выяснении того, как использовать эти конкретные инструменты для решения проблемы. :)

Один метод с использованием sed:

sed -e '1,8d;$d' inputfile

На этом уровне простоты GNU sed и BSD sed работают одинаково. Наш sed-скрипт говорит:

  • 1,8d - удалить строки с 1 по 8,
  • $d - удалить последнюю строку.

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

Другой метод с использованием awk:

awk 'NR>9{print last} NR>1{last=$0}' inputfile

Это работает немного по-другому, чтобы «распознать» последнюю строку, захватить предыдущую строку и распечатать после строки 8, а затем НЕ распечатать последнюю строку.

Это awk-решение немного хакерское, и, как и решение sed, основано на том факте, что вы хотите удалить только ОДНУ последнюю строку файла.

Если вы хотите удалить более одной строки из нижней части файла, вы, вероятно, захотите сохранить массив, который будет функционировать как буферный FIFO или скользящее окно.

awk -v striptop=8 -v stripbottom=3 '
  { last[NR]=$0; }
  NR > striptop*2 { print last[NR-striptop]; }
  { delete last[NR-striptop]; }
  END { for(r in last){if(r<NR-stripbottom+1) print last[r];} }
' inputfile

Вы указываете, сколько раздеться в переменных. Массив last хранит некоторое количество строк в памяти, печатает с дальнего конца стека и удаляет их по мере печати. Секция END перебирает все, что осталось в массиве, и печатает все, что не запрещено stripbottom.

person ghoti    schedule 12.11.2015
comment
Хорошая мысль о двойном цитировании; ваша команда awk должна говорить NR>9, и ее можно оптимизировать, заменив NR>1 на NR>=9 или, в более общем смысле: n=9; awk "NR>$n{print last} NR>=$n{last=\$0}" inputfile - но, как вы утверждаете, это немного хакерски. - person mklement0; 12.11.2015
comment
Спасибо, исправил однострочник awk, и да, это будет оптимизация. Что касается вашего общего подхода, несмотря на другие соображения, я не думаю, что когда-либо буду использовать двойные кавычки для содержания сценария awk - я боюсь расширения переменных внутри таких сценариев. Я бы больше склонялся к: awk -v n="$n" 'NR>n{print last} .... - person ghoti; 12.11.2015
comment
Да, хороший момент в отношении передачи переменных - использование -v - это путь. Я просто срезал путь в этом простом случае. - person mklement0; 12.11.2015