Перенаправлення IO та команда head


9

Я намагався сьогодні швидко відредагувати .hgignoreфайл із оболонки башти Cygwin, і я додав рядок, який був помилкою. Я не впевнений, чи це був найкращий спосіб зробити це, але я швидко подумав про те, head -1 .hgignoreщоб видалити рядок порушень (у мене раніше був лише один рядок у файлі). Звичайно, при виконанні він дає перший рядок як єдиний вихід.

Але коли я спробував перенаправити вихід і переписати файл за допомогою head -1 .hgignore > .hgignore, файл був порожнім. Чому це відбувається? Якщо я спробую замість цього додати head -1 .hgignore >> .hgignore, він додається правильно, але це, очевидно, не бажаний результат. Чому в цьому випадку перенаправлення обрізування не працює?


Відповіді:


10

Коли оболонка отримує командний рядок на кшталт: command > file.outоболонка сама відкриває (і, можливо, створює) названий файл file.out. Оболонка встановлює дескриптор файлу 0 дескриптору файлового файлу, який він отримав з відкритого місця. Ось так працює перенаправлення вводу / виводу: кожен процес знає про дескриптори файлів 0, 1 і 2.

Важкою частиною цього є те, як відкрити file.out. Більшу частину часу вам потрібно file.outвідкрити для запису зі зміщенням 0 (тобто усіченим), і це те, що оболонка зробила для вас. Він урізав .hgignore, відкрив його для запису, скопіював поданий сценарій до 0, а потім виконав head. Миттєве клобірування файлів.

У bash shell ви робите set noclobberзмінити цю поведінку.


Ага, бачу. Я думав, що оболонка обрізала файл перед запуском команди, але не знала, чому. Дякую за пояснення!
voithos

10

Я думаю, що Брюс відповідає, що відбувається тут з оболонкою.

Одна з моїх улюблених маленьких утиліт - це spongeкоманда з moreutils . Він вирішує саме цю проблему, "всмоктуючи" всі наявні вхідні дані, перш ніж відкрити цільовий вихідний файл і записати дані. Це дозволяє записати трубопроводи саме так, як ви очікували:

$ head -1 .hgignore | sponge .hgignore

Рішення бідолахи полягає в передачі вихідного файлу у тимчасовий файл, після закінчення виконання трубопроводу (наприклад, наступна команда, яку ви запускаєте) - переміщення темп-файлу назад у вихідне місце файлу.

$ head -1 .hgingore > .hgignore.tmp
$ mv .hgignore{.tmp,}

Дивлячись на це через кілька років, мені спала на думку думка: чи не могли ми просто зробити head -1 .hgignore | tee .hgignore? teeє в coreutils, і як пік / побічний ефект, це також пише STDOUT
voithos

@voithos Наскільки мені відомо, він teeвідкриває і скорочує файл, в який він записується, коли він створюється як і все інше, щоб він не вирішив тут головну проблему стану гонки щодо читання вмісту файлу, перш ніж обрізати його з записом.
Калеб

Ви підсумовуєте те, про що я насправді не знав, а саме - що трубопровідні команди запускаються негайно, а не послідовно. Це точно? Я, проте, протестував це і, tee здається, виконав бажане. У мене є версія 8.13на моїй машині.
войтос

1
Команди @voithos Так на трубопроводі та всі вхідні / вихідні канали запускаються у зворотному порядку, тому конвеєр готовий приймати дані, коли перший починає його давати. Я підозрюю, що ваш тест є помилковим, тому що ви, ймовірно, використовували занадто малий шматок даних, і він отримав кешування у буфері для читання, перш ніж вам це потрібно. teeПрограма буде вкоротити ваші файли, він не налаштований на подвійний буфер них.
Калеб

3

В

head -n 1 file > file

fileобрізається перед початком headроботи, але якщо ви пишете:

head -n 1 file 1<> file

це не так, fileяк відкрито в режимі читання-запису. Однак, коли headзакінчує запис, він не врізає файл, тому рядок вище був би неоперативним ( headпросто переписав би перший рядок над собою, а інші залишили недоторканими).

Однак після headповернення і, поки fdфункція все ще відкрита, ви можете викликати іншу команду, яка робить truncate.

Наприклад:

{ head -n 1 file; perl -e 'truncate STDOUT, tell STDOUT'; } 1<> file

Тут важливо те, що truncateвище,head просто переміщується курсор на fd 1 у файлі відразу після першого рядка. Він переписує перший рядок, який нам не потрібен, але це не шкідливо.

Маючи POSIX голову, ми могли насправді піти, не переписавши цей перший рядок:

{ head -n 1 > /dev/null
  perl -e 'truncate STDIN, tell STDIN'
} <> file

Тут ми використовуємо той факт, що headпереміщує позицію курсора в його stdin. Хоча head, як правило, читають його великі шматки для підвищення продуктивності, POSIX вимагає від нього (де це можливо)seek повернути відразу після першого рядка, якби він вийшов за його межі. Зауважте, що не всі реалізації це роблять.

Крім того, ви можете використовувати команду оболонки readзамість цього:

{ read -r dummy; perl -e 'truncate STDIN, tell STDIN'; } <> file

1
Стефане, чи знаєте ви стандартну команду або coreutils, яка може усікати STDINаналогічно тому, що ви виконали, використовуючи perlвище
iruvar

2
@ 1_CR, ні. ddможна врізати будь-яке довільне абсолютне зміщення у файлі. Тож ви можете визначити зміщення байтів другого рядка та обрізати звідти за допомогоюdd bs=1 seek="$offset" of=file
Stéphane Chazelas

1

Рішення справжньої людини є

ed .hgignore
$d
wq

або як однолінійний

printf '%s\n' '$d' 'wq' | ed .hgignore

Або з GNU sed:

sed -i '$d' .hgignore

(Ні, я жартую. Я б використовував інтерактивний редактор. vi .hgignore GddZZ)


Я цікавився, чи є якась перевага у використанні :wqнад ZZ?
voithos

Також, :xщо мої пальці роблять автоматично
glenn jackman

і ZQте саме, що:q!
glenn jackman

ZZ і: x пишіть лише тоді, коли є що написати ...: w завжди fsyncs файл на диск незалежно від того, чи потрібен він. Я використовую: xa, тому що я використовую вкладки.
ксенотеррацид

1

Ви можете використовувати Vim в режимі Ex:

ex -sc '2,d|x' .hgignore
  1. 2, виберіть рядки 2 до кінця

  2. d видалити

  3. x зберегти і закрити


0

Для редагування файлів на місці ви можете також використовувати трюк з обробкою відкритого файлу, як показано Jürgen Hötzel у виводі перенаправлення від sed 's / c / d /' myFile до myFile .

exec 3<.hgignore
rm .hgignore  # prevent open file from being truncated
head -1 <&3 > .hgignore

ls -l .hgignore  # note that permissions may have changed

2
І тільки після того, як rm .hgignoreваша влада вийде з ладу, забираючи години важкої праці. Гаразд, це не має значення .hgignore, але чому б ти все-таки робив щось таке складне? Таким чином, моя думка: технічно правильна, але дуже погана ідея.
Жил "ТАК - перестань бути злим"

@ Gilles, можливо, не дуже гарна ідея, але це, наприклад, те, що perl -i(для редагування на місці) робить, і я не здивуюсь, якщо деякі реалізації цього також sed -iзробили (хоча остання версія GNU, sedздається, не).
Стефан Шазелас
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.