Проблема с перенаправлением вывода Bash
Я пытался удалить все строки файла, кроме последней строки, но следующая команда не работала, хотя файл.txt не пуст.
$cat file.txt |tail -1 > file.txt
$cat file.txt
почему это так?
11 ответов
перенаправление из файла через конвейер обратно в тот же файл небезопасно; если 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
вы можете использовать sed для удаления всех строк, кроме последней из файла:
sed -i '$!d' file
- -Я говорит sed заменить файл на месте; в противном случае результат будет записываться в STDOUT.
- $ - это адрес, соответствующий последней строке файла.
- d команда удалить. В этом случае он отрицается !, так что все строки не соответствующий адрес удаленный.
прежде чем " cat "будет выполнен, Bash уже открыл" файл.txt ' для написания, очистки его содержимого.
В общем, не пишите в файлы, которые Вы читаете из того же оператора. Это можно обойти, написав в другой файл, как указано выше:
$cat file.txt | tail -1 >anotherfile.txt $mv anotherfile.txt file.txtили с помощью утилиты, такой как sponge from moreutils:
$cat file.txt | tail -1 | sponge file.txtЭто работает, потому что sponge ждет, пока его входной поток не закончится, прежде чем открыть его выходной файл.
когда вы отправляете свою командную строку в bash, она делает следующее:
- создает канал ввода-вывода.
- запускает "/ usr/bin / tail -1", чтение из трубы и запись в файл.формат txt.
- Starts " /usr/bin / cat файл.txt", надпись на трубе.
к тому времени, как "кошка" начнет читать, " файл.тхт уже обрезан "хвост".
Это все часть дизайна Unix и среды оболочки и возвращает все путь к оригинальной оболочке Борна. Это особенность, а не ошибка.
это хорошо работает в оболочке 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
dd
опция "notrunc" используется для записи отфильтрованного содержимого на место, в то время как dd
требуется снова (с количеством байтов), чтобы фактически усечь файл. Если новый размер файла больше или равен старому размеру файла, второй dd
ссылка не нужна.
преимущества этого по сравнению с методом копирования файлов: 1) нет дополнительного места на диске, 2) более высокая производительность на больших файлах, и 3) чистая оболочка (кроме dd).
как Льюис Baumstark говорит, ему не нравится то, что ты пишешь с тем же именем.
Это потому, что оболочка открывает файл".txt "и усекает его, чтобы сделать перенаправление перед" cat-файлом.txt " запускается. Итак, вы должны
tail -1 file.txt > file2.txt; mv file2.txt file.txt
только для этого случая можно использовать
cat < file.txt | (rm file.txt; tail -1 > file.txt)Что открыть файл".txt "непосредственно перед подключением" cat " с подсхемой в "(...)". - rm-файл.txt " удалит ссылку с диска, прежде чем subshell откроет его для записи для "хвоста", но содержимое будет по-прежнему доступно через открытый дескриптор, который передается "cat", пока он не закроет stdin. Поэтому вам лучше быть уверенным, что эта команда завершит или содержимое "файла.txt" будет потеряна
похоже, вам не нравится, что вы записываете его обратно в то же имя файла. Если вы сделаете следующее, это сработает:
$cat file.txt | tail -1 > anotherfile.txt
tail -1 > file.txt
перезапишет ваш файл, заставляя cat читать пустой файл, потому что повторная запись произойдет до любая из команд в вашем конвейере выполняется.