Jak wykonać dowolną komendę edytując jej plik (argument) „na miejscu” za pomocą basha?

110

Mam plik temp.txt, który chcę posortować za pomocą sortpolecenia w bash.

Chcę, aby posortowane wyniki zastąpiły oryginalny plik.

Na przykład to nie działa (otrzymuję pusty plik):

sortx temp.txt > temp.txt

Czy można to zrobić w jednej linii bez konieczności kopiowania do plików tymczasowych?


EDYCJA: Ta -oopcja jest bardzo fajna sort. Użyłem sortw moim pytaniu przykładu. Mam ten sam problem z innymi poleceniami:

uniq temp.txt > temp.txt.

Czy istnieje lepsze rozwiązanie ogólne?

jm.
źródło
Zobacz także serverfault.com/a/547331/313521
Wildcard

Odpowiedzi:

171
sort temp.txt -o temp.txt
daniels
źródło
3
To jest odpowiedź. Właściwie zastanawiałem się, czy istnieje ogólne rozwiązanie tego problemu. Na przykład, jeśli chcę znaleźć wszystkie wiersze UNIQ w pliku „na miejscu”, nie mogę zrobić -o
jm.
To nie jest ogólne, ale możesz użyć -u z GNU sort, aby znaleźć unikalne linie
James
Czy ktoś rozwiązał problem pozwalając np. sort --inplace *.txt? To byłoby szalenie fajne
patrz
@sehe Spróbuj tego:find . -name \*.txt -exec sort {} -o {} \;
Keith Gaughan
29

A sortmusi zobaczyć wszystkie dane wejściowe, zanim będzie można rozpocząć wyprowadzanie. Z tego powodu sortprogram może łatwo oferować opcję modyfikacji pliku w miejscu:

sort temp.txt -o temp.txt

W szczególności dokumentacja GNUsort mówi:

Zwykle sort czyta wszystkie dane wejściowe przed otwarciem pliku-wyjściowego, więc możesz bezpiecznie posortować plik w miejscu, używając poleceń takich jak sort -o F Fi cat F | sort -o F. Jednak za sortpomocą --merge( -m) można otworzyć plik wyjściowy przed odczytaniem wszystkich danych wejściowych, więc polecenie takie jak cat F | sort -m -o F - Gnie jest bezpieczne, ponieważ sortowanie może rozpocząć zapisywanie Fprzed zakończeniem catodczytu.

Podczas gdy dokumentacja BSD sortmówi:

Jeśli [] plik-wyjściowy jest jednym z plików wejściowych, sort kopiuje go do pliku tymczasowego przed sortowaniem i zapisaniem wyniku do [] pliku-wyjściowego.

Polecenia, takie jak uniqmogą rozpocząć zapisywanie danych wyjściowych, zanim zakończą odczytywanie danych wejściowych. Te polecenia zazwyczaj nie obsługują edycji lokalnej (i byłoby im trudniej obsługiwać tę funkcję).

Zwykle omijasz ten problem z plikiem tymczasowym lub jeśli absolutnie chcesz uniknąć posiadania pliku pośredniego, możesz użyć bufora do przechowywania pełnego wyniku przed jego wypisaniem. Na przykład z perl:

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

Tutaj część perla odczytuje pełne dane wyjściowe ze uniqzmiennej, $_a następnie zastępuje oryginalny plik tymi danymi. Możesz zrobić to samo w wybranym języku skryptowym, być może nawet w Bash. Pamiętaj jednak, że będzie potrzebować wystarczającej ilości pamięci do przechowywania całego pliku, nie jest to zalecane podczas pracy z dużymi plikami.

Bruno De Fraine
źródło
19

Oto bardziej ogólne podejście, działa z uniq, sort i innymi.

{ rm file && uniq > file; } < file
wor
źródło
14
Innym ogólne podejście, ze spongez moreutils: cat file |frobnicate |sponge file.
Tobu
3
@Tobu: dlaczego nie przedstawić tego jako oddzielnej odpowiedzi?
Flimm
1
Prawdopodobnie warto zauważyć, że niekoniecznie zachowuje to uprawnienia do plików. Twoja umask dyktuje, jakie będą nowe uprawnienia.
wor
1
Podstępny. Czy możesz wyjaśnić, jak to dokładnie działa?
patryk.beza
2
@ patryk.beza: W kolejności: Wejście FD jest otwierane z oryginalnego pliku; oryginalny wpis katalogu jest usuwany; przekierowanie jest przetwarzane, tworząc nowy, pusty plik o tej samej nazwie, jaką miał stary; następnie polecenie jest uruchamiane.
Charles Duffy
10

Komentarz Tobu na temat gąbki gwarantuje, że jest odpowiedzią samą w sobie.

Cytat ze strony głównej moreutils :

Prawdopodobnie najbardziej uniwersalnym narzędziem do tej pory w moreutils jest sponge (1), który pozwala robić takie rzeczy:

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

Jednak spongecierpi na ten sam problem, o którym mówi tutaj Steve Jessop. Jeśli którekolwiek z poleceń w potoku spongenie powiedzie się, oryginalny plik zostanie nadpisany.

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

Ups, my-important-fileodszedł.

Sean
źródło
1
Sponge wie, że zostanie użyty do zastąpienia pliku wejściowego i początkowo tworzy plik tymczasowy, aby uniknąć sytuacji wyścigu. Aby to zadziałało, sponge musi być ostatnim elementem w potoku i musi mieć możliwość tworzenia samego pliku wyjściowego (w przeciwieństwie na przykład do przekierowania wyjścia na poziomie powłoki). BTW: Wygląda na to, że łatwą naprawą kodu źródłowego dla przypadku „niepowodzenia” byłoby niezmienianie nazwy pliku tymczasowego w przypadku błędu pipefail (nie wiem, dlaczego sponge nie ma takiej opcji).
Brent Bradburn
Myślę, że jeśli dodasz set -o pipefailna początku swojego skryptu, błąd na mistyped_command my-important-filespowodowałby natychmiastowe zakończenie skryptu, przed wykonaniem sponge, zachowując w ten sposób ważny plik.
Elouan Keryell-Even
6

Proszę bardzo, jedna linia:

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

Z technicznego punktu widzenia nie ma kopiowania do pliku tymczasowego, a polecenie „mv” powinno być natychmiastowe.

davr
źródło
6
Hm. Nadal dzwoniłbym do temp.txt.sort tymczasowego pliku.
JesperE
5
Ten kod jest ryzykowny, ponieważ jeśli sortowanie nie powiedzie się z jakiegokolwiek powodu bez ukończenia zadania, oryginał zostanie nadpisany.
Steve Jessop
1
Brak miejsca na dysku jest prawdopodobną przyczyną lub sygnałem (użytkownik naciska CTRL-C).
Steve Jessop
5
jeśli chcesz użyć czegoś takiego, użyj && (logiczne i) zamiast; ponieważ użycie tego zapewni, że jeśli polecenie zawiedzie, następne nie zostanie wykonane. na przykład: cp backup.tar /root/backup.tar && rm backup.tar Jeśli nie masz uprawnień do kopiowania, będziesz bezpieczny, ponieważ plik nie zostanie usunięty
daniels
1
zmieniłem moją odpowiedź, aby wziąć pod uwagę twoje sugestie, dzięki
davr
4

Podoba mi się sort file -o fileodpowiedź, ale nie chcę dwukrotnie wpisywać tej samej nazwy pliku.

Korzystanie z rozszerzenia historii BASH :

$ sort file -o !#^

po naciśnięciu przechwytuje pierwszy argument bieżącego wiersza enter.

Unikalne sortowanie na miejscu:

$ sort -u -o file !#$

przechwytuje ostatni argument w bieżącej linii.

johnnyB
źródło
3

Wielu wspominało o opcji -o . Oto część strony podręcznika.

Ze strony podręcznika:

   -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
źródło
3

Byłoby to mocno ograniczone do pamięci, ale można by użyć awk do przechowywania danych pośrednich w pamięci, a następnie zapisać je z powrotem.

uniq temp.txt | awk '{line[i++] = $0}END{for(j=0;j<i;j++){print line[j]}}' > temp.txt
JayG
źródło
Myślę, że możliwe jest >obcięcie pliku przed uniqodczytaniem go przez polecenie ( w tym przypadku).
Martin
3

Alternatywa dla spongebardziej powszechnych sed:

sed -ni r<(command file) file

Działa na każdej komendzie ( sort, uniq, tac, ...) i wykorzystuje bardzo dobrze znany sedjest -iopcja (edytowanie plików w miejscu).

Ostrzeżenie: spróbuj command filenajpierw, ponieważ edycja plików na miejscu nie jest z natury bezpieczna.


Wyjaśnienie

Po pierwsze, mówisz sednie do wydrukowania (oryginalna) linii ( -nopcja ), a także z pomocą sed„s rdowodzenia i bash” s Zmiana procesu , generowany przez treść <(command file)będzie wyjście zapisywane w miejscu .


Jeszcze łatwiejsze

Możesz opakować to rozwiązanie w funkcję:

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"
}

Przykład

$ 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
źródło
1

Użyj argumentu --output=lub-o

Właśnie wypróbowałem na FreeBSD:

sort temp.txt -otemp.txt
sammyo
źródło
Chociaż poprawna, jest to po prostu duplikat tej odpowiedzi
hej
1

Aby dodać tę uniqmożliwość, jakie są wady:

sort inputfile | uniq | sort -o inputfile
jaspis
źródło
1

Przeczytać na nieinterakcyjnym edytorze ex.

szczupły
źródło
heh - to totalnie zły pomysł. Lubię to.
David Mackintosh
0

Jeśli nalegasz na użycie sortprogramu, musisz użyć pliku pośredniego - nie sądzę, aby sortmiał opcję sortowania w pamięci. Każda inna sztuczka ze stdin / stdout nie powiedzie się, chyba że możesz zagwarantować, że rozmiar bufora dla standardowego wejścia sortowania jest wystarczająco duży, aby zmieścić się w całym pliku.

Edycja: wstyd mi. sort temp.txt -o temp.txtdziała doskonale.

JesperE
źródło
Czytałem Q również jako „na miejscu”, ale drugi odczyt sprawił, że uwierzyłem, że tak naprawdę o to nie prosił
epatel
0

Inne rozwiązanie:

uniq file 1<> file
Antonio Lebrón
źródło
Należy jednak zauważyć, że <>sztuczka działa tylko w tym przypadku, ponieważ uniqjest wyjątkowa, ponieważ kopiuje tylko linie wejściowe do linii wyjściowych, pomijając niektóre po drodze. Jeśli sedużyto innego polecenia (np. ), Które zmieniłoby wejście (np. Zmieniłoby każde ana aa), to może ono nadpisać filew sposób, który nie ma żadnego sensu, a nawet zapętlić się w nieskończoność, pod warunkiem, że wejście jest wystarczająco duże (więcej niż pojedynczy bufor odczytu).
David