Як виконати будь-яку команду, що редагує її файл (аргумент) "на місці", використовуючи bash?


110

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

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

Наприклад, це не працює (я отримую порожній файл):

sortx temp.txt > temp.txt

Чи можна це зробити в один рядок, не вдаючись до копіювання до тимчасових файлів?


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

uniq temp.txt > temp.txt.

Чи є краще загальне рішення?


Також дивіться serverfault.com/a/547331/313521
Wildcard

Відповіді:


171
sort temp.txt -o temp.txt

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 -o F Fта та cat F | sort -o F. Однак за sortдопомогою --merge( -m) можна відкрити вихідний файл перед читанням усіх вхідних даних, тому команда на зразок cat F | sort -m -o F - Gне є безпечною, оскільки сортування може почати писати, Fперш ніж catбуде прочитано її.

Хоча в документації BSD sortсказано:

Якщо вихідний файл [the] є одним із вхідних файлів, відсортуйте його до тимчасового файлу, перш ніж сортувати та записувати вихід у вихідний файл.

Такі команди, як, наприклад, uniqможуть почати записувати вихід, перш ніж закінчити читання вводу. Ці команди, як правило, не підтримують редагування на місці (і їм буде складніше підтримувати цю функцію).

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

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

Тут частина Perl зчитує повний вихід з uniqзмінної, $_а потім перезаписує вихідний файл із цими даними. Ви можете зробити те ж саме на мові сценаріїв, яку ви обрали, можливо, навіть на Bash. Але зауважте, що для зберігання всього файлу знадобиться достатньо пам'яті, це не доцільно при роботі з великими файлами.


19

Ось більш загальний підхід, що працює з uniq, сортування та що-небудь ще.

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

14
Інший загальний підхід, з spongeвід moreutils: cat file |frobnicate |sponge file.
Тобу

3
@Tobu: чому б не подати це як окрему відповідь?
Flimm

1
Напевно, добре зауважити, що це не обов'язково зберігає права доступу до файлів. Ваш umask диктує, якими будуть нові дозволи.
черв.

1
Хитрий один. Чи можете ви пояснити, як саме це працює?
patryk.beza

2
@ patryk.beza: Порядок: Вхідний FD відкривається з вихідного файлу; оригінальний запис каталогу видалено; перенаправлення обробляється, створюючи новий порожній файл з такою ж назвою, який був раніше; тоді команда запускається.
Чарльз Даффі

10

Коментар Тобу щодо губки вимагає відповіді сам по собі.

Цитувати з домашньої сторінки moreutils :

Напевно, найзагальнішим інструментом у моторних утилітах поки що є губка (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
Губка знає, що він буде використовуватися для заміни вхідного файлу, і він спочатку створює тимчасовий файл, щоб уникнути перегонів. Для того, щоб це працювало, губка повинна бути останнім елементом трубопроводу, і їй слід дозволити створювати сам вихідний файл (на відміну від перенаправлення виводу на рівні оболонки, наприклад). BTW: Мабуть, легким виправленням вихідного коду для випадку "fail" було б не перейменувати тимчасовий файл у випадку pipefail (не знаю, чому губка не має такої опції).
Брент Бредберн

Я думаю, якщо ви додасте 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, якщо у вас немає прав на копіювання, ви будете в безпеці, оскільки файл не буде видалений
daniels

1
змінив мою відповідь, щоб взяти до уваги ваші пропозиції, дякую
дав

4

Мені подобається sort file -o fileвідповідь, але я не хочу вводити одне й те саме ім’я два рази.

Використання розширення історії BASH :

$ sort file -o !#^

під час натискання схоплює перший аргумент поточного рядка enter.

Унікальний сортування на місці:

$ sort -u -o file !#$

схоплює останній аргумент у поточному рядку.


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.

3

Це може бути обмежено пам'яттю, але ви можете використовувати awk для зберігання проміжних даних у пам'яті, а потім записуйте їх назад.

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

Я думаю , що це можливо , що >обрізає файл перед командою ( 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


1

Щоб додати uniqможливості, які недоліки є:

sort inputfile | uniq | sort -o inputfile


0

Якщо ви наполягаєте на використанні sortпрограми, вам доведеться використовувати проміжний файл - я не думаю, sortщо є можливість сортування в пам'яті. Будь-який інший трюк зі stdin / stdout не вдасться, якщо ви не зможете гарантувати, що розмір буфера для stdin сорту досить великий, щоб вмістити весь файл.

Редагувати: сором мені. sort temp.txt -o temp.txtпрацює чудово.


Я читав Q також як "на місці", але друге читання змусило мене повірити, що він насправді не просив цього
epatel

0

Ще одне рішення:

uniq file 1<> file

Слід зазначити, що <>трюк працює лише в цьому випадку, оскільки uniqособливий тим, що він лише копіює вхідні рядки у вихідні рядки, відкидаючи деякі на шляху. Якщо sedбула використана інша команда (наприклад ), яка змінила б вхід (наприклад, змінила б кожен aна aa), то вона може змінюватись fileспособами, які не мають сенсу і навіть нескінченно циклічно, за умови, що вхід є достатньо великим (більше ніж один буфер зчитування).
Девід
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.