Переместить первые N строк вывода в конец без использования временного файла

11

Представьте себе вывод команды вроде

44444
55555
11111
22222
33333

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

11111
22222
33333
44444
55555

Нечто подобное | sed -n '3,5p;1,2p'(что явно не работает, так как sed не заботится о порядке команд).

Петр Унак
источник
2
Почему мы не можем использовать временный файл?
Брайам

Ответы:

13

Просто скопируйте эти строки в буфер удержания (затем удалите их) и, когда в последней строке добавьте содержимое буфера удержания в пространство шаблона:

some command | sed '1,NUMBER{           # in this range
H                                       # append line to hold space and
1h                                      # overwrite if it's the 1st line
d                                       # then delete the line
}
$G'                                     # on last line append hold buffer content

С gnu sedего помощью можно написать

some command | sed '1,NUMBER{H;1h;d;};$G'

Вот еще один способ с ol ' ed(он rперенаправляет вывод some commandв текстовый буфер, а затем mвыводит строки 1,NUMBERпосле последней $):

ed -s <<IN
r ! some command
1,NUMBERm$
,p
q
IN

Обратите внимание, что, как указывалось, они оба потерпят неудачу, если на выходе будет меньше NUMBER+1 строки. Более надежный подход ( gnu sedсинтаксис):

some command | sed '1,NUMBER{H;1h;$!d;${g;q;};};$G'

эта строка удаляет только строки в этом диапазоне, если они не являются последней строкой ( $!d), иначе она перезаписывает пространство шаблона с помощью содержимого буфера удержания ( g) и затем qиспользует (после печати текущего пространства шаблона).

don_crissti
источник
2
sed -e '1,NUMBER{H;1h;d;}' -e '$G'также работает переносимо (обратите внимание, что некоторые sedреализации не могут содержать более нескольких килобайт в пространстве удержания, поэтому NUMBER не может быть там слишком большим)
Стефан Шазелас
@ StéphaneChazelas - спасибо за ввод - я обычно иду с одной командой на строку, поскольку я точно знаю, что она переносима - синтаксис множественных выражений всегда меня немного смущал, например, документы posix говорят: «Правая скобка» должна быть предшествует <newline> ", так что в соответствии с ними не должно быть sed -e '1,NUMBER{H;1h;d' -e'}' -e '$G'?
don_crissti
4
Как правило, новый -eзаменяет новую строку. d;}это еще не POSIX, но портативный. Это будет исправлено в следующей спецификации POSIX. См. Austingroupbugs.net/view.php?id=944#c2720
Стефан
2
@don_crissti спасибо! было бы здорово, если бы вы могли также включить краткое объяснение того, почему это работает. (Конечно, я пойду посмотрю, но это даст более ценный ответ.)
Питер Унак
В моей голове непереносимость 1,NUMBER{H;1h;d;}заключается в отсутствии точки с запятой сразу после открывающей скобки. Это могло быть просто ошибкой в ​​SunOS 4.1, sedчей обходной путь все еще остается у меня в руках 20 лет спустя.
zwol
11

awkПодход:

cmd | awk -v n=3 '
  NR <= n {head = head $0 "\n"; next}
  {print}
  END {printf "%s", head}'

Одно из преимуществ подхода @ don_crisstised состоит в том, что он все еще работает (выводит строки), если на выходе есть nстроки или меньше.

Стефан Шазелас
источник
Вы можете заменить жесткий код \nна ORS, чтобы он работал с другими разделителями записей (например, вы хотите использовать абзацы и т. Д.).
Федорки
6

У меня есть xclipи с этим это можно сделать следующим образом:

./a_command | xclip -in && xclip -o | tail -n +3 && xclip -o | head -n 2

Вот его описание:

xclip - command line interface to X selections (clipboard)

NAME
       xclip - command line interface to X selections (clipboard)

SYNOPSIS
       xclip [OPTION] [FILE]...

DESCRIPTION
       Reads from standard in, or from one or more files, and makes the data available as an X selection for pasting into X applications. Prints current X selection to standard out.

       -i, -in
              read text into X selection from standard input or files (default)

       -o, -out
              prints the selection to standard out (generally for piping to a file or program)

источник
3
+1 за творческое (неправильное) использование xclip. Для ответа нужен доступный / работающий X-сервер.
Джофель
3

Способ Perl:

perl -ne '$.<3?($f.=$_):print;}{print $f'

Или то же самое, написанное менее загадочно:

perl -ne 'if($.<3){ $f.=$_ } else{ print } END{print $f}'

Например:

$ cat file
44444
55555
11111
22222
33333

$ cat file | perl -ne '$.<3?($f.=$_):print;}{print $f'
11111
22222
33333
44444
55555

объяснение

  • -ne: прочитайте входной файл / поток построчно и примените скрипт, заданный -eдля каждой строки.
  • $.<3: $.номер текущей строки, поэтому измените 3на количество строк, которое вы хотите сместить.
  • $.<3?($f.=$_):print;: это условный оператор, общий формат condition ? case1 : case2, он будет работать, case1если conditiontrue и case2если false. Здесь, если текущий номер строки меньше 3, он добавляет текущую строку ( $_) к переменной $fи, если номер строки больше 3, печатает.
  • }{ print $f: }{это сокращение от perl END{}. Он будет запущен после обработки всех входных строк. На этом этапе мы соберем все строки, которые мы хотим сдвинуть, и напечатаем все те, которые мы хотим оставить в покое, поэтому выведите строки, сохраненные как $f.
Тердон
источник
1
Что касается вашей версии для игры в гольф, можно удалить несколько персонажейperl -ne '$.<3?$f.=$_:print}{print $f
123
1

Используйте POSIX ex. Да, он предназначен для редактирования файлов, но он будет работать в конвейере.

printf %s\\n 111 222 333 444 555 | ex -sc '1,2m$|%p|q!' /dev/stdin

Это может иметь любые произвольные команды, добавленные в начале или конце конвейера, и они будут работать одинаково. Еще лучше, учитывая наличие /dev/stdin, это POSIX-совместимый.

(Я не знаю, /dev/stdinуказано ли это в POSIX или нет, но я вижу, что оно присутствует как в Linux, так и в Mac OS X.)

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

NB. exКоманда, приведенная выше, не будет выполнена, если в качестве входных данных указано менее 2 строк.

Дальнейшее чтение:

Wildcard
источник
0

Короткий pythonфрагмент:

#!/usr/bin/env python3
import sys
file_ = sys.argv[1]
lines = int(sys.argv[2])
with open(file_) as f:
    f = f.readlines()
    out = f[lines:] + f[:lines]
    print(''.join(out), end='')

Передайте имя файла в качестве первого аргумента и количество строк для перемещения в качестве второго аргумента.

Пример:

$ cat input.txt
44444
55555
11111
22222
33333

$ ./sw_lines.py input.txt 2
11111
22222
33333
44444
55555

$ ./sw_lines.py input.txt 4
33333
44444
55555
11111
22222
heemayl
источник
0

Если вы можете сохранить весь вывод в памяти:

data=$(some command)
n=42                  # or whatever
{ tail -n +$((n+1)) <<<"$data"; head -n $n <<<"$data"; } > outputfile
Гленн Джекман
источник
0

Вот еще один вариант, который требует GNU sed:

(x=$(gsed -u 3q);cat;printf %s\\n "$x")

-uделает GNU sedнебуферизованным, чтобы sedкоманда не занимала более 3 строк STDIN. Подстановка команды удаляет пустые строки, так что команда не включает пустые строки в конце вывода, если третья, третья и вторая или третья, вторая и первая строки пусты.

Вы также можете сделать что-то вроде этого:

tee >(sponge /dev/stdout|sed -u 3q)|sed 1,3d

Без sponge /dev/stdout|команды не получится с длинными входами. sponge /dev/stdoutтакже может быть заменен на tac|tac, даже если это печатает, например, a\ncbесли ввод - a\nb\ncгде \nперевод строки, или с (x=$(cat);printf %s\\n "$x"), даже если это удаляет пустые строки с конца ввода. Приведенная выше команда удаляет первую строку, когда количество строк ввода равно одной или двум.

nisetama
источник