Проблема с перенаправлением вывода Bash - PullRequest
10 голосов
/ 23 сентября 2008

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

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

$cat file.txt

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

Ответы [ 11 ]

19 голосов
/ 23 сентября 2008

Перенаправление из файла через конвейер обратно в тот же файл небезопасно; если 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
5 голосов
/ 24 сентября 2008

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

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

Перед выполнением 'cat' Bash уже открыл для записи файл 'file.txt', очистив его содержимое.

В общем, не пишите в файлы, из которых вы читаете, в одном выражении. Это можно обойти, записав в другой файл, как указано выше:

$cat file.txt | tail -1 >anotherfile.txt
$mv anotherfile.txt file.txt
или с помощью утилиты, такой как sponge из moreutils :
$cat file.txt | tail -1 | sponge file.txt
Это работает, потому что sponge ожидает окончания входного потока перед открытием выходного файла.
2 голосов
/ 25 марта 2010

Это хорошо работает в оболочке Linux:

replace_with_filter() {
  local filename="$1"; 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).

2 голосов
/ 23 сентября 2008

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

2 голосов
/ 23 сентября 2008

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

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

К тому времени, когда «cat» начинает читать, «file.txt» уже был обрезан «tail».

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

1 голос
/ 25 марта 2010
echo "$(tail -1 file.txt)" > file.txt
1 голос
/ 25 марта 2010

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

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

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

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

tail -1 file.txt > file2.txt; mv file2.txt file.txt
0 голосов
/ 23 сентября 2008

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...