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

110

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

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

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

sortx temp.txt > temp.txt

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


РЕДАКТИРОВАТЬ: -oвариант очень крутой для sort. Я использовал sortв своем вопросе в качестве примера. У меня такая же проблема с другими командами:

uniq temp.txt > temp.txt.

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

JM.
источник
Также см. Serverfault.com/a/547331/313521
Wildcard

Ответы:

171
sort temp.txt -o temp.txt
Daniels
источник
3
Это ответ. Мне действительно было интересно, есть ли общее решение этой проблемы. Например, если я хочу найти все строки UNIQ в файле «на месте», я не могу выполнить -o
jm.
Это не универсально, но вы можете использовать -u с сортировкой GNU для поиска уникальных строк
Джеймс,
Кто-нибудь решил проблему, например, разрешить sort --inplace *.txt? Это было бы безумно круто
см.
@sehe Попробуйте это:find . -name \*.txt -exec sort {} -o {} \;
Кейт Гоган,
29

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

sort temp.txt -o temp.txt

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

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

В документации BSD sortсказано:

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

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

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

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

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

Бруно де Фрайн
источник
19

Вот более общий подход, работает с uniq, sort и так далее.

{ rm file && uniq > file; } < file
Wor
источник
14
Другой общий подход, с spongeот moreutils: cat file |frobnicate |sponge file.
Tobu,
3
@Tobu: почему бы не отправить это как отдельный ответ?
Flimm
1
Вероятно, стоит отметить, что это не обязательно сохраняет права доступа к файлам. Ваша umask определяет, какими будут новые разрешения.
Wor
1
Хитрый. Вы можете объяснить, как это работает?
patryk.beza
2
@ patryk.beza: По порядку: входной FD открывается из исходного файла; исходная запись каталога удаляется; перенаправление обрабатывается, создается новый пустой файл с тем же именем, что и старый; затем команда запускается.
Чарльз Даффи
10

Комментарий Тобу о губке требует самостоятельного ответа.

Цитата с домашней страницы 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

Ой-ой, my-important-fileушел.

Шон
источник
1
Sponge знает, что он будет использоваться для замены входного файла, и изначально создает временный файл, чтобы избежать состояния гонки. Чтобы это работало, sponge должен быть последним элементом в конвейере, и ему должно быть разрешено создавать сам выходной файл (в отличие от перенаправления вывода на уровне оболочки, например). Кстати: кажется, что простое исправление исходного кода для случая «сбоя» - не переименовывать временный файл в случае сбоя конвейера (не знаю, почему у sponge нет этой опции).
Brent Bradburn
Я думаю, что если вы добавите set -o pipefailв начале своего скрипта, ошибка mistyped_command my-important-fileзаставит скрипт немедленно завершиться, прежде чем он будет выполнен sponge, таким образом сохраняя важный файл.
Элуан Кериелл-Эвен
6

Вот и все, одна строка:

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

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

Давр
источник
6
Хм. Я бы по-прежнему называл temp.txt.sort временным файлом.
JesperE
5
Этот код опасен, потому что, если сортировка по какой-либо причине завершается неудачно, не завершив свою работу, оригинал перезаписывается.
Стив Джессоп,
1
Недостаток дискового пространства является вероятной причиной или сигналом (пользователь нажимает CTRL-C).
Стив Джессоп,
5
если вы хотите использовать что-то подобное, используйте && (логическое и) вместо; потому что использование этого гарантирует, что в случае сбоя команды следующая не будет выполнена. например: cp backup.tar /root/backup.tar && rm backup.tar, если у вас нет прав на копирование, вы будете в безопасности, так как файл не будет удален
Дэниелс
1
изменил свой ответ, чтобы принять во внимание ваши предложения, спасибо
davr
4

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

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

$ sort file -o !#^

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

Уникальная сортировка на месте:

$ sort -u -o file !#$

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

johnnyB
источник
3

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

На странице руководства:

   -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.
epatel
источник
3

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

uniq temp.txt | awk '{line[i++] = $0}END{for(j=0;j<i;j++){print line[j]}}' > temp.txt
JayG
источник
Я думаю, что возможно, что >файл обрезается до того, как команда ( uniqв данном случае) его прочитает.
Мартин
3

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

sed -ni r<(command file) file

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

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


объяснение

Во - первых, вы говорите , sedне печатать (оригинальное) линии ( -nопция ), а также с помощью sed«s rкоманды и bash» s Подстановка процессов , генерируемый контент путем <(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
whoan
источник
1

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

Только что попробовал на FreeBSD:

sort temp.txt -otemp.txt
Sammyo
источник
Хотя правильный, это просто дубликат этого ответа
whoan
1

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

sort inputfile | uniq | sort -o inputfile
Джаспер
источник
1

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

тонкий
источник
хех - это абсолютно злая идея. Мне это нравится.
Дэвид Макинтош
0

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

Изменить: позор мне. sort temp.txt -o temp.txtработает отлично.

JesperE
источник
Я прочитал Q также как «на месте», но второе прочтение заставило меня поверить, что он на самом деле не просил об этом
epatel
0

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

uniq file 1<> file
Антонио Леброн
источник
Однако следует отметить, что этот <>трюк работает только в этом случае, потому uniqчто он особенный в том, что он только копирует входные строки в выходные строки, удаляя некоторые по пути. Если использовалась другая команда (например sed), которая изменила бы ввод (например, изменила бы каждый aна aa), тогда она может переопределить fileспособами, которые не имеют никакого смысла, и даже бесконечный цикл, при условии, что ввод достаточно велик (больше, чем одиночный буфер чтения).
Дэвид