




Кажется, не нравится тот факт, что вы записываете его обратно с тем же именем файла. Если вы сделаете следующее, это сработает:
$cat file.txt | tail -1 > anotherfile.txt
Перенаправление из файла через конвейер обратно в тот же файл небезопасно; если file.txt перезаписывается оболочкой при настройке последней стадии конвейера до того, как tail начнет чтение с первой стадии, вы получите пустой вывод.
Вместо этого сделайте следующее:
tail -1 file.txt >file.txt.new && mv file.txt.new file.txt
... ну, вообще-то, не делайте этого в производственном коде; особенно если вы находитесь в среде, где важна безопасность, и работаете от имени пользователя root, более уместно следующее:
tempfile = "$(mktemp file.txt.XXXXXX)"
chown --reference=file.txt -- "$tempfile"
chmod --reference=file.txt -- "$tempfile"
tail -1 file.txt >"$tempfile" && mv -- "$tempfile" file.txt
Другой подход (избегание временных файлов, если <<< неявно создает их на вашей платформе) заключается в следующем:
lastline = "$(tail -1 file.txt)"; cat >file.txt <<<"$lastline"
(Вышеупомянутая реализация специфична для bash, но работает в тех случаях, когда echo не работает, например, когда последняя строка содержит «--version»).
Наконец, можно использовать губку из Moreutils:
tail -1 file.txt | sponge file.txt
Обратите внимание, что tail принимает в качестве аргумента имя файла: "tail -1 file.txt> file.txt.new && mv file.txt.new file.txt"
@Marcel Levy - совершенно верно, и таким образом он может работать более эффективно; обновлено.
Уважаемый @CharlesDuffy, я хотел бы отметить, что ваш «безопасный» вариант требует, чтобы это делал пользователь root. Что касается того, насколько это безопасно, я могу сказать, что при доступе к "file.txt" недостаточно применить его разрешения и владельца. Учтите, что файл «file.txt» имеет права доступа 0666 и помещен в папку «~ / .ssh» с разрешениями 0700. В этом случае мое решение будет более безопасным, поскольку оно не будет открывать этот файл миру. Должен ли я из-за этого проголосовать против вашего ответа? ;)
@CharlesDuffy, о ... Я совершенно забыл, что вы можете потерять данные, если кто-то добавит одну строчку в этот файл, потому что вы открываете ее в течение всего времени tail -1 file.txt. В результате вы можете перезаписать существующий file.txt старым содержимым. Должен ли я из-за этого проголосовать против вашего ответа? ;)
@ony Использование chown потенциально полезно здесь, если файл принадлежит вторичной группе, и требует ли нет привилегий в этом случае.
@ony Что касается состояния гонки - если бы этого можно было избежать, конечно, проголосуйте против меня. Я хотел бы услышать, как будет сделано это избегание (особенно учитывая, что sed -i, ed, ex и kin тоже не атомарны).
@CharlesDuffy, для файла ~/.ssh/file.txt существует несколько уровней разрешений - сначала ~ (и все его родительские папки), затем .ssh (обычно это 0700) и затем file.txt. Вы копируете только разрешения для последнего элемента, что потенциально снижает уровень безопасности. В моем решении этого можно избежать, используя один и тот же файл (без перемещения / копирования / перезаписи). Так что этой проблемы можно избежать - это означает, что я могу проголосовать против? Конечно, у моего решения есть другая проблема - размер файла может отличаться от его содержимого, но это другая проблема. В моем первоначальном ответе я также избегаю проблем с разрешениями.
@ony Это серьезная проблема - временный файл должен создаваться в конечном месте, а не с использованием TEMPDIR. Внесение этого изменения; спасибо за указатель.
я думаю, что TEMPFILE = $ (tempfile) намного проще ... конечно (нога в рот) не в каждом дистрибутиве есть это до сих пор? интересно, почему, если так "безопаснее" ..
@osirisgothra, действительно, tempfile зависит от дистрибутива. Недоступно в OS X, недоступно в самой последней версии Arch Linux и т. д. Кроме того, пользователю полезно знать программному обеспечению, с чем связан данный временный файл или каталог, что делает параметр шаблона ценной функцией.
Как говорит Льюис Баумстарк, не нравится, что вы пишете в одно и то же имя файла.
Это связано с тем, что оболочка открывает файл «file.txt» и усекает его, чтобы выполнить перенаправление перед запуском «cat file.txt». Итак, вам нужно
tail -1 file.txt > file2.txt; mv file2.txt file.txt
tail -1 > file.txt перезапишет ваш файл, заставив cat прочитать пустой файл, потому что произойдет повторная запись до Выполняется любая из команд в вашем конвейере.
Перед запуском cat Bash уже открыл файл file.txt для записи, очищая его содержимое.
В общем, не выполняйте запись в файлы, которые вы читаете, в одном и том же операторе. Это можно обойти, записав в другой файл, как указано выше:
$cat file.txt | tail -1 >anotherfile.txt $mv anotherfile.txt file.txtor by using a utility like sponge from Moreutils:
$cat file.txt | tail -1 | sponge file.txtThis works because sponge waits until its input stream has ended before opening its output file.
Имеет ли cat какое-либо значение по сравнению с tail -1 file.txt? Это, безусловно, делает далеко менее эффективным, если ваш file.txt большой - если tail имеет дескриптор файла с прямым поиском, он может перейти прямо к последнему килобайту файла, прочитать только его и попытаться найти последнюю строку, сделав резервную копию, если последняя строка больше 1кб; если все, что у него есть, - это канал от cat, он должен прочитать файл с самого начала - независимо от его размера - чтобы добраться до конца.
... tail -1 <file.txt также будет реальным дескриптором файла с возможностью поиска и на стандартном вводе (таким образом, идентичным аргументам командной строки, переданным вашему текущему подходу cat file.txt | tail -1).
Когда вы отправляете свою командную строку в bash, она выполняет следующие действия:
К тому времени, когда cat начинает читать, file.txt уже усечен словом tail.
Все это часть дизайна Unix и среды оболочки и восходит к исходной оболочке Bourne. «Это особенность, а не ошибка.
Указан ли порядок между (2) и (3)? У меня сложилось впечатление, что они происходят одновременно, что фактически приводит к возникновению гонки - хотя file.txt открывается для записи до того, как /usr/bin/tail будет запущен, поэтому усечение с высокой вероятностью выиграет гонку (поскольку /usr/bin/cat требует exec со всеми влияние на производительность компоновщика / загрузчика, что подразумевает).
tmp = $ (хвост -1 файл.txt); echo $ tmp> file.txt;
отлично избегает временных файлов, но его следует указывать, чтобы избежать всех стандартных проблем.
Вы можете использовать sed для удаления всех строк, кроме последней, из файла:
sed -i '$!d' file
Единственный прискорбный момент в том, что sed -i не совместим с POSIX, а является расширением GNU. Использование ed или ex для редактирования на месте позволит избежать этих недостатков.
Это прекрасно работает в оболочке Linux:
replace_with_filter() {
local filename = ""; shift
local dd_output byte_count filter_status dd_status
dd_output=$("$@" <"$filename" | dd conv=notrunc of = "$filename" 2>&1; echo "${PIPESTATUS[@]}")
{ read; read; read -r byte_count _; read filter_status dd_status; } <<<"$dd_output"
(( filter_status > 0 )) && return "$filter_status"
(( dd_status > 0 )) && return "$dd_status"
dd bs=1 seek = "$byte_count" if=/dev/null of = "$filename"
}
replace_with_filter file.txt tail -1
Параметр «notrunc» dd используется для записи отфильтрованного содержимого обратно на место, в то время как dd требуется снова (со счетчиком байтов) для фактического усечения файла. Если размер нового файла больше или равен размеру старого файла, второй вызов dd не требуется.
Преимущества этого метода по сравнению с методом копирования файлов: 1) отсутствие необходимости в дополнительном дисковом пространстве, 2) более высокая производительность для больших файлов и 3) чистая оболочка (кроме dd).
Мне это нравится - это новаторская идея. Я только сейчас не поддерживаю его, потому что использование $FILTER противоречит mywiki.wooledge.org/BashFAQ/050 (использование разбиения строк для правильного формирования команды чрезвычайно чревато ошибками).
Надеюсь, вы не возражаете против моих деспотичных правок; теперь это решение должно быть значительно более надежным.
Я бы не назвал его replace_with_filter. Это будет работать только для фильтров, которые с каждым следующим блоком производят данные не больше, чем исходный блок. В противном случае информация, прочитанная фильтром, может быть перезаписана его выводом. Обратите внимание, что вместо последнего dd вы можете использовать truncate -s $byte_count "$filename".
@ony truncate не является стандартным для POSIX инструментом - он присутствует в современных coreutils, но существует множество платформ, где bash доступен, но его нет.
Как раз в этом случае можно использовать
cat < file.txt | (rm file.txt; tail -1 > file.txt)That will open "file.txt" just before connection "cat" with subshell in "(...)". "rm file.txt" will remove reference from disk before subshell will open it for write for "tail", but contents will be still available through opened descriptor which is passed to "cat" until it will close stdin. So you'd better be sure that this command will finish or contents of "file.txt" will be lost
Анонимные противники должны быть наказаны. Этот ответ работает и имеет логику (т.е. удалить файл после его открытия для чтения, чтобы сохранить доступ к содержимому).
Мне трудно не согласиться с (предполагаемым) утверждением, что следует избегать решения, в котором случаи отказа приводят к потере данных, даже если это предостережение четко обозначено; ненужная подоболочка - это вишенка на торте. А «наказали»? Действительно?
@CharlesDuffy, "должен быть наказан" для анонима. Потому что отрицательный голос без каких-либо причин раздражает. Вы так не думаете? .. Что касается правильности, вы уверены, что для правильного ответа требуется высокая безопасность и надежность? Иногда можно чем-то пожертвовать ради архивации чего-то другого. Таким образом, с разных точек зрения вы можете по-разному относиться к решениям. Если вы спроецируете мой ответ на свое мнение, вы обнаружите, что предварительное условие для этого ответа не может быть выполнено, поэтому было бы неправильной логикой говорить, что это неверно.
... кстати, чтобы было ясно, я не был анонимным голосом против; Я просто считаю их действия оправданными.
@CharlesDuffy, хорошо, это ваше предположение насчет того, кто проголосовал против. Но на самом деле причина может быть в другом. И вот в чем проблема.
Неправильно цитирую: «Если бы строители строили дома так, как разработчики создают программы, первый появившийся дятел уничтожит цивилизацию». Предположение, что люди используют излишне хрупкие подходы как нечто само собой разумеющееся (при отсутствии конкретной причины для иного), приводит к тому, что программное обеспечение и системы пронизаны неожиданными и недокументированными режимами отказов и, таким образом, увековечивают проблему.
@CharlesDuffy, факт замены содержимого файла его последней строкой - это уже неправильный путь. Это должны быть либо два отдельных файла (если кому-то нужно все содержимое), либо только одна строка в этом файле (т.е. одна строка, а не все содержимое), чтобы не было необходимости делать с ним tail -1. И я почти уверен, что разнообразие вариантов здесь связано с тем, что это больше похоже на головоломку.
Оперативное редактирование - это то, что делают люди. tail -1 может быть не особенно хорошим вариантом использования (на самом деле, это точно не так), но любой, кто имеет карьеру в системном администрировании, будет с нетривиальной частотой использовать методы, аналогичные тем, которые здесь встречаются в реальных производственных средах и сценариях. Таким образом, нам надлежит - «головоломка» или нет - поощрять принятие хороших практик и препятствовать принятию плохих.
echo "$(tail -1 file.txt)" > file.txt
Что будет, если последняя строка содержит только -n? Что, если он содержит литералы с обратной косой чертой и ваш echo совместим с расширениями XSI POSIX (которые требуют, чтобы любые escape-последовательности, которые эти литералы могли образовывать, учитывались даже без чего-либо, похожего на -e)? Было бы безопаснее использовать printf '%s\n' "$(tail -1 file.txt)", чем полагаться на эхо
Нет необходимости в предположениях типа «кажется»: нет никакой гарантии упорядочения между запуском компонентов конвейера, и перенаправления (таким образом, открытие
anotherfile.txtв режиме усечения) происходят. Выполнение до (в данном случаеtail; происходит ли это до выполненияcatне определено, но поскольку такой программе, какcat, требуется время для запуска, очень вероятно, что усечениеanotherfile.txtуже произойдет до того, какcatзавершит загрузку и будет готов открыть свой аргумент для ввода).