Проблема с перенаправлением вывода Bash

Я пытался удалить все строки файла, кроме последней строки, но следующая команда не работала, хотя file.txt не пуст.

$cat file.txt |tail -1 > file.txt

$cat file.txt

Почему это так?

Ответ 1

Перенаправление из файла по конвейеру обратно в тот же файл небезопасно; если 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

Ответ 2

Вы можете использовать sed для удаления всех строк, кроме последнего из файла:

sed -i '$!d' file
  • -i сообщает sed, чтобы заменить файл на месте; в противном случае результат будет записываться в STDOUT.
  • $ - это адрес, который соответствует последней строке файла.
  • d - команда удаления. В этом случае он сбрасывается !, поэтому все строки, не соответствующие адресу, будут удалены.

Ответ 3

Прежде чем "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
Это работает, потому что губка ждет, пока ее входной поток не закончится, прежде чем открыть свой выходной файл.

Ответ 4

Когда вы отправляете свою командную строку в bash, она делает следующее:

  • Создает канал ввода-вывода.
  • Запускает "/usr/bin/tail -1", считывает из канала и записывает в файл .txt.
  • Запускает "/usr/bin/cat file.txt", записывая в трубку.

К моменту начала чтения "cat" файл file.txt уже был усечен "хвостом".

Это вся часть дизайна Unix и среды оболочки, и она полностью возвращается к исходной оболочке Bourne. "Это особенность, а не ошибка.

Ответ 5

tmp = $(tail -1 file.txt); echo $tmp > file.txt;

Ответ 6

Это прекрасно работает в оболочке 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).

Ответ 7

Как говорит Льюис Баумстарк, ему не нравится, что вы пишете одно имя.

Это потому, что оболочка открывает файл "file.txt" и усекает его, чтобы выполнить перенаправление до запуска "cat file.txt". Итак, вы должны

tail -1 file.txt > file2.txt; mv file2.txt file.txt

Ответ 8

Только для этого случая можно использовать

cat < file.txt | (rm file.txt; tail -1 > file.txt)
Это откроет файл "file.txt" перед соединением "cat" с подоболочкой в ​​ "(...)". "rm file.txt" удалит ссылку с диска, прежде чем подоболочка откроет ее для записи для "хвоста", но содержимое будет по-прежнему доступно через открытый дескриптор, который передается "cat", пока он не закроет stdin. Поэтому вы должны быть уверены, что эта команда завершится или будет потеряно содержимое "file.txt"

Ответ 9

echo "$(tail -1 file.txt)" > file.txt

Ответ 10

Кажется, не нравится тот факт, что вы записываете его обратно в одно и то же имя файла. Если вы выполните следующее, это будет работать:

$cat file.txt | tail -1 > anotherfile.txt

Ответ 11

tail -1 > file.txt перезапишет ваш файл, в результате чего кошка будет читать пустой файл, так как перезапись произойдет до. Выполняется любая из команд в вашем конвейере.