Как выполнить любую команду, редактирующую ее файл (аргумент) "на месте", используя bash?

У меня есть файл temp.txt, который я хочу сортировать с помощью команды sort в bash.

Я хочу, чтобы отсортированные результаты заменили исходный файл.

Это не работает, например (я получаю пустой файл):

sortx temp.txt > temp.txt

Можно ли это сделать в одной строке, не прибегая к копированию во временные файлы?


EDIT: параметр -o очень крут для sort. Я использовал sort в моем вопросе в качестве примера. Я сталкиваюсь с той же проблемой с другими командами:

uniq temp.txt > temp.txt.

Есть ли лучшее общее решение?

Ответ 1

sort temp.txt -o temp.txt

Ответ 2

A sort должен видеть все входные данные, прежде чем он сможет начать вывод. По этой причине программа sort может легко предоставить возможность изменить файл на месте:

sort temp.txt -o temp.txt

В частности, в документации документации GNU sort говорится:

Обычно сортировка считывает все входные данные перед открытием выходного файла, поэтому вы можете безопасно сортировать файл на месте с помощью команд типа sort -o F F и cat F | sort -o F. Тем не менее, sort с --merge (-m) может открыть выходной файл перед чтением всего ввода, поэтому команда типа cat F | sort -m -o F - G небезопасна, так как сортировка может начать запись F, прежде чем cat будет прочитано.

Пока в документации BSD sort говорится:

Если [the] output-file является одним из входных файлов, сортируйте его во временный файл перед сортировкой и записью вывода в выходной файл.

Команды, такие как uniq, могут начать запись вывода до завершения чтения ввода. Эти команды обычно не поддерживают редактирование на месте (и им будет сложнее поддерживать эту функцию).

Обычно вы работаете с временным файлом или если вы абсолютно не хотите иметь промежуточный файл, вы можете использовать буфер для хранения полного результата перед его записью. Например, при perl:

uniq temp.txt | perl -e 'undef $/; $_ = <>; open(OUT,">temp.txt"); print OUT;'

Здесь часть perl считывает полный вывод из uniq в переменной $_, а затем перезаписывает исходный файл этими данными. Вы можете сделать то же самое на языке сценариев по вашему выбору, возможно, даже в Bash. Но учтите, что для хранения всего файла потребуется достаточно памяти, это не рекомендуется при работе с большими файлами.

Ответ 3

Здесь более общий подход, работает с uniq, sort и whatnot.

{ rm file && uniq > file; } < file

Ответ 4

Комментарий Тобу о губке гарантирует, что это ответ сам по себе.

Чтобы процитировать страницу moreutils:

Наверное, наиболее универсальным инструментом в moreutils пока является sponge (1), который позволяет вам делать такие вещи:

% sed "s/root/toor/" /etc/passwd | grep -v joey | sponge /etc/passwd

Однако sponge страдает от той же проблемы здесь комментарии Дж. Джесопа. Если какая-либо из команд в конвейере до sponge завершается с ошибкой, тогда оригинал файл будет записан поверх.

$ mistyped_command my-important-file | sponge my-important-file
mistyped-command: command not found

Uh-oh, my-important-file отсутствует.

Ответ 5

Здесь вы идете, одна строка:

sort temp.txt > temp.txt.sort && mv temp.txt.sort temp.txt

Технически нет копирования во временный файл, а команда "mv" должна быть мгновенной.

Ответ 6

Мне нравится ответ sort file -o file, но вы не хотите вводить одно и то же имя файла дважды.

Использование BASH расширение истории:

$ sort file -o !#^

захватывает текущую строку сначала arg, когда вы нажимаете enter.

Уникальный вид на месте:

$ sort -u -o file !#$

захватывает последний аргумент в текущей строке.

Ответ 7

Многие упомянули опцию -o. Вот часть страницы man.

На странице man:

   -o output-file
          Write output to output-file instead of to the  standard  output.
          If  output-file  is  one of the input files, sort copies it to a
          temporary file before sorting and writing the output to  output-
          file.

Ответ 8

Альтернатива sponge с более распространенным sed:

sed -ni r<(command file) file

Он работает для любой команды (sort, uniq, tac,...) и использует очень хорошо известную sed -i (редактировать файлы на месте).

Предупреждение: Попробуйте command file сначала, потому что редактирование файлов на месте не безопасно по своей природе.


Объяснение

Во-первых, вы сообщаете sed не печатать (оригинальные) строки (-n вариант), а с помощью команды sed r и bash Замена процесса, сгенерированный контент <(command file) будет отображаться с сохраненным на месте.


Сделать вещи еще проще

Вы можете перенести это решение в функцию:

ip_cmd() { # in place command
    CMD=${1:?You must specify a command}
    FILE=${2:?You must specify a file}
    sed -ni r<("$CMD" "$FILE") "$FILE"
}

Пример

$ cat file
d
b
c
b
a

$ ip_cmd sort file
$ cat file
a
b
b
c
d

$ ip_cmd uniq file
$ cat file
a
b
c
d

$ ip_cmd tac file
$ cat file
d
c
b
a

$ ip_cmd
bash: 1: You must specify a command
$ ip_cmd uniq
bash: 2: You must specify a file

Ответ 9

Это будет сильно ограничено памятью, но вы можете использовать awk для хранения промежуточных данных в памяти, а затем записать его обратно.

uniq temp.txt | awk '{line[i++] = $0}END{for(j=0;j<i;j++){print line[j]}}' > temp.txt

Ответ 10

Прочитайте неинтерактивный редактор ex.

Ответ 11

Используйте аргумент --output= или -o

Просто попробовал FreeBSD:

sort temp.txt -otemp.txt

Ответ 12

Чтобы добавить возможность uniq, каковы нижние грани для:

sort inputfile | uniq | sort -o inputfile

Ответ 13

Если вы настаиваете на использовании программы sort, вы должны использовать промежуточный файл - я не думаю, что sort имеет возможность сортировки в памяти. Любой другой трюк с stdin/stdout не удастся, если вы не можете гарантировать, что размер буфера для сортировки stdin достаточно велик, чтобы соответствовать всему файлу.

Править: стыдно за меня. sort temp.txt -o temp.txt отлично работает.

Ответ 14

Другое решение:

uniq file 1<> file