Додайте останній рядок stdin до всього stdin


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"

Так що він все-таки виробляє однаковий результат у цьому іншому контексті?

ПРИМІТКА: Конкретний Гередок, якого я вловлюю, а також використання самого Гередока є лише показовим. Будь-яка прийнятна відповідь повинна припускати, що він отримує довільні дані через stdin .


1
stdin - це завжди "фактичний файл" (fifo / socket / тощо - це теж файл; не всі файли можна шукати). Відповідь на ваше запитання - це банальне "використовувати тимчасовий файл", або якийсь жах, який завантажить весь файл у пам'ять. "Як я можу отримати старі дані з потоку, не зберігаючи їх ніде ?" не може мати хорошої відповіді.
mosvy

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

З іншого боку, ми, звичайно, можемо використовувати тут-doc:

$ 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.

    У дивовижній частині ORS- це розділювач записів на виході . За замовчуванням це символ нового рядка.

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

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

Оскільки це зчитує весь вхід у пам'ять, воно не буде доречним для великих ( наприклад, гігабайт) входів.


Дякую, Джон. Тож чи не можливо це зробити аналогічно моєму прикладу файлу в ОП? Я уявляв, як stdin якось дублюється ... teeподібний спосіб , але для stdin і файлів ми би розв'язували той самий stdin у дві різні підстановки процесу. чи щось, що було б приблизно рівнозначно цьому?
Йона

5

Якщо stdin вказує на файл, який можна шукати (як, наприклад, у випадку з bash's (але не всі інші оболонки), тут документи, реалізовані з тимчасовими файлами), ви можете отримати хвіст, а потім шукати назад, перш ніж прочитати повний вміст:

Оператори пошуку доступні в оболонках 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будь-якою оболонкою, яка використовує тимчасові файли для тут-документів, ви можете фактично використовувати файл temp, створений тут-документом, для зберігання результатів:

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 KiB; але він рекомендує, щоб цей буфер динамічно розподілявся та розширювався за необхідності, що і 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є важливим, оскільки вони виконуються у поданій послідовності.


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

Взагалі уникайте обробки тексту шаблону тут doc; спробуйте створити його за допомогою інтерпольованого коду.


1
Я, чесно кажучи, не можу сказати, це жарт чи ні. В cat <<EOS...ОП було лише прикладом позиції щодо "введення довільного файлу", щоб зробити публікацію конкретною і питання зрозумілим. Це було вам насправді не очевидно, чи ви просто думали, що було б розумно тлумачити питання буквально?
Йона

@Jonah Питання чітко говорить про те, що "[l] et's говорять, що наше джерело введення, а не фактичний файл, було замість stdin:". Нічого про "довільні файли"; мова йде тут про документи. Тут документ не є довільним. Це не вхід до вашої програми, а фрагмент її синтаксису, який програміст вибирає.
Каз

1
Я думаю, що з контексту та існуючих відповідей було зрозуміло, що це було так, хоча б тому, що, щоб ваше тлумачення було правильним, ви буквально повинні були припустити, що ні я, ні будь-який інший, хто відповів, не зрозуміли, що можна скопіювати та вставити папку рядок коду. Тим не менш, я відредагую питання, щоб зробити його явним.
Йона

1
Каз, дякую за відповідь, але зауважте, що навіть при редакції, ви не вистачаєте наміру питання. Ви отримуєте довільний багаторядковий вхід через трубу . Ви поняття не маєте, що це буде. Ваше завдання - вивести останній рядок входу, за яким слід весь вхід.
Йона

1
Kaz, вхід є лише як приклад. Більшість людей, включаючи мене, вважають корисним приклад реального вкладу та очікуваного результату, а не лише абстрактне питання. Ти єдиний, хто це збентежив.
Йона

0

Якщо ви не дбаєте про замовлення. Тоді це спрацює cat lines | tee >(tail -1). Як казали інші. Вам потрібно прочитати файл двічі або буферувати весь файл, щоб зробити це у порядку, про який ви просили.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.