Как я могу использовать файл в команде и перенаправить вывод в тот же файл без его усечения?
В основном я хочу взять в качестве входного текста из файла, удалить строку из этого файла и отправить вывод обратно в тот же файл. Что-то вдоль этих строк, если это делает его более ясным.
grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name > file_name
однако, когда я делаю это, я получаю пустой файл.
Любые мысли?
Ответы
Ответ 1
Вы не можете сделать это, потому что bash сначала обрабатывает перенаправления, а затем выполняет команду. Таким образом, к тому времени, как grep просматривает имя_файла, оно уже пусто. Вы можете использовать временный файл, хотя.
#!/bin/sh
tmpfile=$(mktemp)
grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name > ${tmpfile}
cat ${tmpfile} > file_name
rm -f ${tmpfile}
вот так, рассмотрите возможность использования mktemp
для создания tmpfile, но учтите, что это не POSIX.
Ответ 2
Используйте sponge для таких задач. Его часть moreutils.
Попробуйте выполнить следующую команду:
grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name | sponge file_name
Ответ 3
Используйте sed вместо:
sed -i '/seg[0-9]\{1,\}\.[0-9]\{1\}/d' file_name
Ответ 4
попробуйте этот простой
grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name | tee file_name
В этот раз ваш файл не будет пустым:) и ваш вывод также будет распечатан на ваш терминал.
Ответ 5
Вы не можете использовать оператор перенаправления (>
или >>
) для одного и того же файла, потому что он имеет более высокий приоритет, и он создаст/урежет файл до того, как команда будет даже вызвана. Чтобы избежать этого, вы должны использовать соответствующие инструменты, такие как tee
, sponge
, sed -i
или любой другой инструмент, который может записывать результаты в файл (например, sort file -o file
).
По сути, перенаправление ввода в один и тот же исходный файл не имеет смысла, и вы должны использовать для этого соответствующие редакторы на месте, например, редактор Ex (часть Vim):
ex '+g/seg[0-9]\{1,\}\.[0-9]\{1\}/d' -scwq file_name
где:
-
'+cmd'
/-c
- запустить любую команду Ex/Vim -
g/pattern/d
- удалить строки, соответствующие шаблону, используя global (help :g
) -
-s
- бесшумный режим (man ex
) -
-c wq
- команды execute :write
и :quit
Вы можете использовать sed
, чтобы достичь того же (как уже было показано, в других ответах), однако на месте (-i
) не является -s tandard расширение FreeBSD (может работать по- разному между Unix/Linux) и в основном это s Tream изд Итор, а не редактор файлов. См.: Есть ли практический режим Ex-режима?
Ответ 6
Один альтернативный вариант liner - задайте содержимое файла как переменную:
VAR=`cat file_name`; echo "$VAR"|grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' > file_name
Ответ 7
Там также ed
(в качестве альтернативы sed -i
):
# cf. http://wiki.bash-hackers.org/howto/edit-ed
printf '%s\n' H 'g/seg[0-9]\{1,\}\.[0-9]\{1\}/d' wq | ed -s file_name
Ответ 8
Вы можете использовать slurp с POSIX Awk:
!/seg[0-9]\{1,\}\.[0-9]\{1\}/ {
q = q ? q RS $0 : $0
}
END {
print q > ARGV[1]
}
Пример
Ответ 9
Вы можете сделать это с помощью process-substitution.
Это немного взломать, но как bash асинхронно открывает все трубы, и нам нужно обойти это с помощью sleep
, поэтому YMMV.
В вашем примере:
grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name > >(sleep 1 && cat > file_name)
-
>(sleep 1 && cat > file_name)
создает временный файл, который получает результат grep
-
sleep 1
задерживается на секунду, чтобы дать grep время для анализа входного файла
- наконец
cat > file_name
записывает вывод
Ответ 10
Поскольку этот вопрос является главным результатом в поисковых системах, здесь один-liner из https://serverfault.com/a/547331, который использует подоболочку вместо sponge
(которая часто не входит в состав ванильной установки, такой как OS X):
echo "$(grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name)" > file_name
Или общий случай:
echo "$(cat file_name)" > file_name
Тест с https://askubuntu.com/a/752451:
printf "hello\nworld\n" > file_uniquely_named.txt && for ((i=0; i<1000; i++)); do echo "$(cat file_uniquely_named.txt)" > file_uniquely_named.txt; done; cat file_uniquely_named.txt; rm file_uniquely_named.txt
Должен печатать:
hello
world
В то время как вызов cat file_uniquely_named.txt > file_uniquely_named.txt
в текущей оболочке:
printf "hello\nworld\n" > file_uniquely_named.txt && for ((i=0; i<1000; i++)); do cat file_uniquely_named.txt > file_uniquely_named.txt; done; cat file_uniquely_named.txt; rm file_uniquely_named.txt
Распечатывает пустую строку.
Я не тестировал это на больших файлах (возможно, более 2 или 4 ГБ).
Я взял этот ответ от Харта Симха и Кос.
Ответ 11
Обычно я использую программу tee для этого:
grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name | tee file_name
Он создает и удаляет временный файл сам по себе.
Ответ 12
Попробуй это
echo -e "AAA\nBBB\nCCC" > testfile
cat testfile
AAA
BBB
CCC
echo "$(grep -v 'AAA' testfile)" > testfile
cat testfile
BBB
CCC
Ответ 13
Далее будет сделать то же самое, что sponge
делает, не требуя moreutils
:
shuf --output=file --random-source=/dev/zero
--random-source=/dev/zero
part shuf
делать свое дело без всяких перемешиваний, поэтому он буферизует ваш ввод, не изменяя его.
Тем не менее, это правда, что использование временного файла лучше всего по соображениям производительности. Итак, вот функция, которую я написал, которая сделает это для вас в обобщенном виде:
# Pipes a file into a command, and pipes the output of that command
# back into the same file, ensuring that the file is not truncated.
# Parameters:
# $1: the file.
# $2: the command. (With $3... being its arguments.)
# See https://stackoverflow.com/a/55655338/773113
function siphon
{
local tmp=$(mktemp)
local file="$1"
shift
$* < "$file" > "$tmp"
mv "$tmp" "$file"
}
Ответ 14
возможно, вы можете сделать это вот так:
grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name | cat > file_name