Добавить последнюю строку стандартного ввода ко всему стандартному вводу

9

Рассмотрим этот скрипт:

tmpfile=$(mktemp)

cat <<EOS > "$tmpfile"
line 1
line 2
line 3
EOS

cat <(tail -1 "$tmpfile") "$tmpfile"

Это работает и выводит:

line 3
line 1
line 2
line 3

Допустим, что наш источник ввода, а не фактический файл, был вместо этого stdin:

cat <<EOS | # what goes here now?
line 1
line 2
line 3
EOS

Как мы модифицируем команду:

cat <(tail -1 "$tmpfile") "$tmpfile"

Так что он все еще производит тот же результат, в этом другом контексте?

ПРИМЕЧАНИЕ: конкретный Heredoc, которым я занимаюсь, а также использование самого Heredoc, является просто иллюстративным. Любой приемлемый ответ должен предполагать, что он получает произвольные данные через стандартный ввод .

Ион
источник
1
stdin - это всегда «фактический файл» (fifo / socket / etc тоже файл; не все файлы доступны для поиска). Ответом на ваш вопрос является либо тривиальное «использование временного файла», либо какой-то ужас, который загрузит весь файл в память. «Как я могу извлечь старые данные из потока, не сохраняя их где-либо ?» не может иметь хороший ответ.
Мосви
1
@mosvy Это вполне приемлемый ответ, если вы хотите добавить его.
Иона
2
@mosvy Как сказал Иона, ответы должны быть размещены в поле для ответов. Я знаю, что сейчас сложно читать любой сайт, но, пожалуйста, не обращайте внимания на красный, который медленно стекает по вашему зрению, и используйте нижнюю текстовую область.
wizzwizz4

Ответы:

7

Пытаться:

awk '{x=x $0 ORS}; END{printf "%s", $0 ORS x}'

пример

Определите переменную с помощью нашего ввода:

$ input="line 1
> line 2
> line 3"

Запустите нашу команду:

$ echo "$input" | awk '{x=x $0 ORS}; END{printf "%s", $0 ORS x}'
line 3
line 1
line 2
line 3

В качестве альтернативы, конечно, мы могли бы использовать здесь документ:

$ cat <<EOS | awk '{x=x $0 ORS}; END{printf "%s", $0 ORS x}'
line 1
line 2
line 3
EOS
line 3
line 1
line 2
line 3

Как это работает

  • x=x $0 ORS

    Это добавляет каждую строку ввода к переменной x.

    В awk ORS- разделитель выходных записей . По умолчанию это символ перевода строки.

  • END{printf "%s", $0 ORS x}

    После того как мы прочитали весь файл, будет напечатана последняя строка $0, за которой следует содержимое всего файла x.

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

John1024
источник
Спасибо Джон. Так нельзя ли сделать это способом, аналогичным моему примеру с именованным файлом в OP? Я представлял, что stdin как-то дублируется ... вроде как tee, но из stdin и файла мы бы передавали один и тот же stdin в две разные подстановки процесса. или что-нибудь, что было бы примерно эквивалентно этому?
Иона
5

Если stdin указывает на доступный для поиска файл (как, например, в случае документов bash (но не всех других оболочек), которые реализованы с временными файлами), вы можете получить хвост и затем выполнить обратный поиск перед чтением полного содержимого:

Операторы поиска доступны в оболочках zshили ksh93, или в языках сценариев, таких как tcl / perl / python, но не в bash. Но вы всегда можете позвонить этим более продвинутым переводчикам, bashесли вам придется их использовать bash.

ksh93 -c 'tail -n1; cat <#((0))' <<...

Или

zsh -c 'zmodload zsh/system; tail -n1; sysseek 0; cat' <<...

Теперь это не сработает, когда stdin указывает на файлы без возможности поиска, такие как канал или сокет. Тогда единственный вариант - прочитать и сохранить (в памяти или во временном файле ...) весь ввод.

Некоторые решения для хранения в памяти уже были даны.

С помощью временного файла zshвы можете сделать это с помощью:

seq 10 | zsh -c '{ cat =(sed \$w/dev/fd/3); } 3>&1'

Если на Linux, с bashили zshили любой оболочкой , что файлы используют временные для здесь-документов, вы могли бы реально использовать временный файл , созданный с помощью документ-здесь , чтобы сохранить результат:

seq 10 | {
  chmod u+w /dev/fd/3 # only needed in bash5+
  cat > /dev/fd/3
  tail -n1 /dev/fd/3
  cat <&3
} 3<<EOF
EOF
Стефан Шазелас
источник
4
cat <<EOS | sed -ne '1{h;d;}' -e 'H;${G;p;}'
line 1
line 2
line 3
EOS

Проблема с переводом этого к чему-то, что использует то, tailчто tailнужно прочитать весь файл, чтобы найти его конец. Чтобы использовать это в своем конвейере, вам нужно

  1. Предоставьте полное содержание документа tail.
  2. Обеспечить его снова в cat.
  3. В этой последовательности.

Сложность заключается не в том, чтобы дублировать содержимое документа ( teeделает это), а в том, чтобы получить результат tailдо того, как будет выведен остальной документ, без использования промежуточного временного файла.

Использование sed(или awk, как это делает John1024 ) избавляет от двойного анализа данных и проблемы упорядочения, сохраняя данные в памяти.

sedРешение , которое я предлагаю , чтобы

  1. 1{h;d;}, сохраните первую строку в пространстве удержания, как есть, и перейдите к следующей строке.
  2. H, добавьте каждую строку к пробелу со встроенной новой строкой.
  3. ${G;p;}, добавьте пробел к последней строке со встроенной новой строкой и напечатайте полученные данные.

Это довольно буквальный перевод решения John1024 в sed, с оговоркой, что стандарт POSIX гарантирует только то, что пространство удержания составляет по крайней мере 8192 байта (8 КиБ; но он рекомендует, чтобы этот буфер динамически выделялся и расширялся по мере необходимости, что оба GNU sedи BSD sedделает).


Если вы позволите себе использовать именованный канал:

mkfifo mypipe
cat <<EOS | tee mypipe | cat <( tail -n 1 mypipe ) -
line 1
line 2
line 3
EOS
rm -f mypipe

Это используется teeдля отправки данных вниз mypipeи в то же время cat. catУтилита первого чтения вывода tail(который считывает с mypipe, который teeпишет в), а затем добавить копию документа , приходящий непосредственно из tee.

Однако в этом есть серьезный недостаток, заключающийся в том, что если документ слишком большой (больше, чем размер буфера канала), teeзапись mypipeи catблокирование будут выполняться в ожидании опустошения (без имени) канала. Он не будет опустошен, пока не catпрочитан из него. catне будет читать с него, пока tailне закончил. И tailне закончил бы, пока teeне закончил. Это классическая тупиковая ситуация.

Вариация

tee >( tail -n 1 >mypipe ) | cat mypipe -

имеет ту же проблему.

Кусалананда
источник
2
sedОдин не работает , если вход имеет только одну строку (может быть sed '1h;1!H;$!d;G'). Также обратите внимание, что несколько sedреализаций имеют низкий предел размера своего шаблона и места для хранения.
Стефан
Решение с именованными каналами - это то, что я искал. Ограничение это позор. Я понял ваше объяснение, за исключением того, что «А хвост не закончится, пока не закончится мишень» - не могли бы вы пояснить, почему это так?
Иона
2

Существует инструмент, названный peeв наборе утилит командной строки, обычно упакованный с именем «moreutils» (или иным образом доступный на его домашнем веб-сайте ).

Если вы можете иметь его в своей системе, то эквивалент для вашего примера будет выглядеть так:

cat <<EOS | pee 'tail -1' cat 
line 1
line 2
line 3
EOS

Порядок выполнения команд peeважен, потому что они выполняются в предоставленной последовательности.

LL3
источник
1

Пытаться:

cat <<EOS # | what goes here now? Nothing!
line 3
line 1
line 2
line 3
EOS

Поскольку все это буквальные данные («документ здесь-это»), а разница между ним и желаемым выводом тривиальна, просто помассируйте эти буквальные данные прямо в соответствии с выводом.

Теперь предположим, что он line 3откуда-то и хранится в переменной с именем lastline:

cat <<EOS # | what goes here now? Nothing!
$lastline
line 1
line 2
$lastline
EOS

В этом документе мы можем генерировать текст, подставляя переменные. Не только это, но мы можем вычислить текст с помощью подстановки команд:

cat <<EOS
this is template text
here we have a hex conversion: $(printf "%x" 42)
EOS

Мы можем интерполировать несколько строк:

cat <<EOS
multi line
preamble
$(for x in 3 1 2 3; do echo line $x ; done)
epilog
EOS

В общем, избегайте текстовой обработки здесь шаблона документа; попытаться сгенерировать его, используя интерполированный код.

Kaz
источник
1
Я честно не могу сказать, если это шутка или нет. Операция cat <<EOS...в OP была просто примером для «кошки произвольного файла», чтобы конкретизировать пост и прояснить вопрос. Было ли это на самом деле неочевидным для вас, или вы просто подумали, что было бы разумно толковать вопрос буквально?
Иона
@Jonah В вопросе четко сказано: «[l] et's говорят, что наш источник ввода, а не фактический файл, вместо этого был stdin:». Ничего о "произвольных файлах"; это здесь документы А вот документ не произвольный. Это не вход в вашу программу, а часть ее синтаксиса, которую выбирает программист.
Каз
1
Я думаю, что контекст и существующие ответы ясно дали понять, что это имело место, хотя бы потому, что для правильной интерпретации вы буквально должны были предположить, что ни я, ни кто-либо из ответивших авторов не осознали, что можно скопировать и вставить строка кода. Тем не менее, я отредактирую вопрос, чтобы сделать его явным.
Иона
1
Kaz, спасибо за ответ, но заметьте, что даже с вашей правкой, вы не поняли намерения вопроса. Вы получаете произвольный многострочный ввод через канал . Вы понятия не имеете, что это будет. Ваша задача - вывести последнюю строку ввода, за которой следует весь ввод.
Иона
1
Kaz, вход есть только в качестве примера. Большинство людей, включая меня, считают полезным иметь реальный вклад и ожидаемый результат, а не только абстрактный вопрос. Вы единственный, кто был смущен этим.
Иона
0

Если вы не заботитесь о заказе. Тогда это будет работать cat lines | tee >(tail -1). Как уже говорили другие. Вам нужно прочитать файл дважды или поместить в буфер весь файл, чтобы сделать это в том порядке, в котором вы просили.

Ctrl-Alt-Delor
источник