Я пытался удалить все строки файла, кроме последней строки, но следующая команда не работала, хотя file.txt не пуст.
$cat file.txt |tail -1 > file.txt
$cat file.txt
Почему это так?
Я пытался удалить все строки файла, кроме последней строки, но следующая команда не работала, хотя file.txt не пуст.
$cat file.txt |tail -1 > file.txt
$cat file.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 -специфична, но работает в тех случаях, когда эхо не работает - например, когда последняя строка содержит "-версию", например).
Наконец, можно использовать губку из moreutils:
tail -1 file.txt | sponge file.txt
Вы можете использовать sed для удаления всех строк, кроме последнего из файла:
sed -i '$!d' file
Прежде чем "cat" будет выполнен, Bash уже открыл файл "file.txt" для записи, очистив его содержимое.
В общем, не пишите в файлы, которые вы читаете из одного и того же оператора. Это можно обойти, записав в другой файл, как указано выше:
$cat file.txt | tail -1 >anotherfile.txt $mv anotherfile.txt file.txtили с помощью утилиты, такой как губка из moreutils:
$cat file.txt | tail -1 | sponge file.txtЭто работает, потому что губка ждет, пока ее входной поток не закончится, прежде чем открыть свой выходной файл.
Когда вы отправляете свою командную строку в bash, она делает следующее:
К моменту начала чтения "cat" файл file.txt уже был усечен "хвостом".
Это вся часть дизайна Unix и среды оболочки, и она полностью возвращается к исходной оболочке Bourne. "Это особенность, а не ошибка.
tmp = $(tail -1 file.txt); echo $tmp > file.txt;
Это прекрасно работает в оболочке Linux:
replace_with_filter() {
local filename="$1"; shift
local dd_output byte_count filter_status dd_status
dd_output=$("[email protected]" <"$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
dd
Опция "notrunc" используется для записи отфильтрованного содержимого обратно на место, а dd
требуется снова (с байтом) для фактического обрезания файла. Если размер нового файла больше или равен размеру старого файла, второй вызов dd
не требуется.
Преимущества этого метода копирования файлов: 1) не требуется дополнительное дисковое пространство, 2) более высокая производительность для больших файлов и 3) чистая оболочка (отличная от dd).
Как говорит Льюис Баумстарк, ему не нравится, что вы пишете одно имя.
Это потому, что оболочка открывает файл "file.txt" и усекает его, чтобы выполнить перенаправление до запуска "cat file.txt". Итак, вы должны
tail -1 file.txt > file2.txt; mv file2.txt file.txt
Только для этого случая можно использовать
cat < file.txt | (rm file.txt; tail -1 > file.txt)Это откроет файл "file.txt" перед соединением "cat" с подоболочкой в "(...)". "rm file.txt" удалит ссылку с диска, прежде чем подоболочка откроет ее для записи для "хвоста", но содержимое будет по-прежнему доступно через открытый дескриптор, который передается "cat", пока он не закроет stdin. Поэтому вы должны быть уверены, что эта команда завершится или будет потеряно содержимое "file.txt"
echo "$(tail -1 file.txt)" > file.txt
Кажется, не нравится тот факт, что вы записываете его обратно в одно и то же имя файла. Если вы выполните следующее, это будет работать:
$cat file.txt | tail -1 > anotherfile.txt
tail -1 > file.txt
перезапишет ваш файл, в результате чего кошка будет читать пустой файл, так как перезапись произойдет до. Выполняется любая из команд в вашем конвейере.