Использование головы и хвоста для захвата различных наборов строк и сохранения в одном файле

10

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

Мне нужно использовать голову и хвост, чтобы получить разные наборы строк из одного файла. Как строки 6-11 и строки 19-24 и сохраните их в другой файл. Я знаю, что могу сделать это с помощью дополнения, таких как

head -11 file|tail -6 > file1; head -24 file| tail -6 >> file1. 

Но я не думаю, что мы должны.
Есть ли конкретный способ, которым я мог бы объединить команды head и tail и затем сохранить в файл?

user2709291
источник
1
Они специально просят вас использовать headи tail? Если это так, то ваше решение - самое лучшее, что вы можете сделать. Если вам разрешено использовать другие программы sedили вы awkможете использовать более приятные решения (например, с меньшим количеством вызовов процессов).
n.st
Да, они просят нас использовать голову и хвост. Спасибо за ваш ответ.
user2709291
Еще одна вещь , которую я могу добавить: Вы можете получить вокруг прилагаемой перенаправление вывода ( >>) приложив две команды в скобках , чтобы перенаправить их каскадный выход: (head -11 file | tail -6; head -24 file | tail -6) > file1. Это действительно сводится к личным предпочтениям, которые приятнее.
n.st
Спасибо, что сработает очень хорошо. Я очень ценю это.
user2709291

Ответы:

11

Вы можете сделать это с помощью headодной и основной арифметики, если вы группируете команды с { ... ; }использованием такой конструкции, как

{ head -n ...; head -n ...; ...; } < input_file > output_file

где все команды имеют одинаковый ввод (спасибо @mikeserv ).
Получение строк 6-11 и строк 19-24 эквивалентно:

head -n 5 >/dev/null  # dump the first 5 lines to `/dev/null` then
head -n 6             # print the next 6 lines (i.e. from 6 to 11) then
head -n 7 >/dev/null  # dump the next 7 lines to `/dev/null` ( from 12 to 18)
head -n 6             # then print the next 6 lines (19 up to 24)

Итак, в основном вы бы запустили:

{ head -n 5 >/dev/null; head -n 6; head -n 7 >/dev/null; head -n 6; } < input_file > output_file
don_crissti
источник
У меня не работает. Ввод поглощен энтузиазмом первой головы
Причудливый
6

Вы можете использовать { … }конструкцию группировки, чтобы применить оператор перенаправления к составной команде.

{ head -n 11 file | tail -n 6; head -n 24 file | tail -n 6; } >file1

Вместо дублирования первых M + N строк и сохранения только последних N, вы можете пропустить первые M строк и продублировать следующие N. Это значительно быстрее для больших файлов . Помните, что +Nаргумент tail- это не количество пропускаемых строк, а еще один плюс - номер первой строки для печати со строками, пронумерованными от 1.

{ tail -n +6 file | head -n 6; tail -n +19 file | head -n 6; } >file1

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

{ tail -n +6 | head -n 6; tail -n +14 | head -n 6; } <file >file1

В общем, это не работает. (Это может работать в некоторых системах, по крайней мере, когда ввод является обычным файлом.) Почему? Из-за входной буферизации . Большинство программ, в том числе tail, не читают свои входные байты за байтом, но несколько килобайт за раз, потому что это быстрее. Таким образом, tailчитает несколько килобайт, пропускает немного в начале, передает немного больше headи останавливается - но то, что читается, читается и не доступно для следующей команды.

Другой подход заключается в использовании headpiped /dev/nullдля пропуска строк.

{ head -n 5 >/dev/null; head -n 6; head -n 7 >/dev/null; head -n 6; } <file >file1

Опять же, это не гарантирует работу из-за буферизации. Это происходит при работе с headкомандой из GNU coreutils (найденной в не встроенных системах Linux), когда входные данные поступают из обычного файла. Это потому, что, как только эта реализация headпрочитала, что она хочет, она устанавливает позицию файла в первый байт, который она не вывела. Это не работает, если вход представляет собой канал.

Более простой способ напечатать несколько последовательностей строк из файла - вызвать более универсальный инструмент, такой как sed или awk . (Это может быть медленнее, но это имеет значение только для очень больших файлов.)

sed -n -e '6,11p' -e '19,24p' <file >file1
sed -e '1,5d' -e '12,18d' -e '24q' <file >file1
awk '6<=NR && NR<=11 || 19<=NR && NR<=24' <file >file1
awk 'NR==6, NR==11; NR==19, NR==24' <file >file1
Жиль "ТАК - перестань быть злым"
источник
2
Это не работает, это стандартное, заданное поведение - хотя, как вы говорите, канал не является надежным источником ввода для совместного ввода. ОПИСАНИЕ ПО УМОЛЧАНИЮ ПО УМОЛЧАНИЮ : Когда стандартная утилита считывает искомый входной файл и завершает работу без ошибки до того, как он достигает конца файла, утилита будет гарантировать, что смещение файла в описании открытого файла правильно расположено сразу после последнего байта, обработанного утилита
mikeserv
2

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

$ cat foo
a 1 1
a 2 1
b 1 1
a 3 1
c 3 1
c 3 1
$ sed -ne '2,4p;6p' foo
a 2 1
b 1 1
a 3 1
c 3 1

Вы даже можете собрать блоки в строку с другим процессом и запустить его через sed.

$ a="2,4p;6p"
$ sed -ne $a foo
a 2 1
b 1 1
a 3 1
c 3 1

-n отрицает вывод, затем вы указываете диапазоны для печати с p, где первый и последний номер диапазона разделяются запятой.

При этом вы можете либо выполнить группировку команд, предложенную @don_crissti, либо несколько раз перебрать файл, когда голова / хвост захватывает кусок строки при каждом прохождении.

$ head -4 foo | tail -3; head -6 foo | tail -1
a 2 1
b 1 1
a 3 1
c 3 1

Чем больше строк в файле и чем больше блоков, тем эффективнее будет работать sed.

Falsenames
источник
2

С sedвами можно сделать:

sed '24q;1,5d;12,18d' <infile >outfile

... Возможно, более эффективное решение может быть найдено с head. Дон уже продемонстрировал, как это может работать очень хорошо, но я тоже с этим поиграл. Что-то, что вы могли бы сделать, чтобы справиться с этим конкретным случаем:

for   n in 5 6 7 6
do    head -n"$n" >&"$((1+n%2))"
done  <infile >outfile 2>/dev/null

... который будет вызывать head4 раза, записывая либо в, outfileлибо в /dev/nullзависимости от того, является ли значение этой итерации $nчетным или нечетным числом.

Для более общих случаев я собрал это вместе с некоторыми другими вещами, которые у меня уже были:

somehead()( 
### call it like:
### somehead -[repeat] [-][numlines]* <infile >outfile
    set -e -- "${1#-}" "$@"                             #-e for arg validation
    r=; cd -- "${TMP:-/tmp}"                            #go to tmp
    dd bs=4096 of="$$$$" <&4 2>&3 &                     #dd <in >tmpfile &bg
    until [ -s "$$$$" ]; do :; done                     #wait while tmpfile empty
    exec <"$$$$" 4<&-;   rm "$$$$"                      #<tmpfile; rm tmpfile
    [ "$3${1}0" -ne "$3${2#?}0" ]          ||           #validate args - chk $1
            shift "$(((r=-${1:--1})||1))"; shift        #shift 1||2
    while [ "$(((r+=(_n=1))-1))" -ne 0 ]   &&           #while ! $rptmax &&
          IFS= read -r l                   &&           #      ! EOF     &&
          printf "%.$(($1>0?${#l}+1:0))s" "$l           #      ? printf  do
";  do    for n do [ "${n#-}" -gt 0 ]      || exit      #args all -[nums>0]
          head "-n$((${n#-}-_n))" >&"$((n>(_n=0)?1:3))" #head -n?$1 >?[+-]
    done; done                                          #done and done
)   4<&0 3>/dev/null                                    #4<for dd 3>for head

Это может сделать вашу вещь, как:

 seq 100 | somehead -1 -5 6 -7 6

... который печатает ...

6
7
8
9
10
11
19
20
21
22
23
24

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

Для каждого следующего аргумента он будет интерпретировать отрицательное целое число, чтобы указать количество строк, в которое следует записать, /dev/nullи положительное целое число, чтобы указать количество строк, в которое следует записать stdout.

Таким образом, в приведенном выше примере он печатает первые 5 строк до /dev/null, следующие 6 до stdout, следующие 7 до /dev/nullснова и следующие 6 еще раз до stdout. Достигнув последнего из его аргументов и полностью циклически -1повторяя счетчик, он затем выходит. Если бы это был первый аргумент, -2он бы повторил процесс еще раз или, если -мог, так долго, как мог.

Для каждого цикла arg цикл whileобрабатывается один раз. В верхней части каждого цикла первая строка из stdinчитается в переменную оболочки $l. Это необходимо, потому что while head </dev/null; do :; doneбудет повторяться до бесконечности - headуказывает в своем возвращении, когда он достиг конца файла. Таким образом, проверка на EOF посвящена readи printfзапишет $lплюс новую строку, stdoutтолько если второй аргумент является положительным целым числом.

readПроверка усложняет петлю немного , потому что сразу же после того, как другой цикл называется - forцикл , который перебирает арг , 2-$#как представлено в $nкаждой итерации родительского whileцикла. Это означает, что для каждой итерации первый аргумент должен быть уменьшен на единицу от значения, указанного в командной строке, но все остальные должны сохранять свои исходные значения, и поэтому значение $_nмаркера var вычитается из каждого, но только когда-либо содержит значение больше 0 для первого аргумента

Это составляет основной цикл функции, но основная часть кода находится вверху и предназначена для того, чтобы функция могла чисто буферизовать даже канал в качестве входных данных. Это работает, сначала вызывая фон ddдля копирования его в tmpfile на выходе с размерами блоков 4k за штуку. Затем функция устанавливает цикл удержания - который почти никогда не должен завершать даже один полный цикл - просто для того, чтобы гарантировать, что ddбыла произведена хотя бы одна запись в файл, прежде чем функция затем заменит свой стандартный ввод дескриптором файла, связанным с tmpfile, и после этого сразу же отменяет связь файла сrm, Это позволяет функции надежно обрабатывать поток, не требуя перехватов или других действий для очистки - как только функция освобождает свою заявку на fd, tmpfile перестанет существовать, поскольку ее единственная именованная ссылка файловой системы уже удалена.

mikeserv
источник
0

Используйте функцию bash, например:

seq 1 30 > input.txt
f(){ head $1 input.txt | tail $2 >> output.txt ;}; f -11 -2; f -24 -3
cat output.txt
10
11
22
23
24

В этом случае это немного излишне, но если ваши фильтры растут больше, это может стать благом.

mkalkov
источник