Zasadniczo chcę wziąć tekst wejściowy z pliku, usunąć wiersz z tego pliku i wysłać dane wyjściowe z powrotem do tego samego pliku. Coś w tym kierunku, jeśli to uczyni to jaśniejszym.
grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name > file_name
jednak kiedy to robię, otrzymuję pusty plik. jakieś pomysły?
Odpowiedzi:
Nie możesz tego zrobić, ponieważ bash najpierw przetwarza przekierowania, a następnie wykonuje polecenie. Więc zanim grep spojrzy na nazwa_pliku, jest już pusty. Możesz jednak użyć pliku tymczasowego.
#!/bin/sh tmpfile=$(mktemp) grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name > ${tmpfile} cat ${tmpfile} > file_name rm -f ${tmpfile}
w ten sposób rozważ użycie
mktemp
do utworzenia pliku tmp, ale pamiętaj, że nie jest to POSIX.źródło
>
przekierowanie otworzy plik i obetnie go przed uruchomieniem powłokigrep
.sponge
polecenia .Do tego typu zadań używaj gąbki . Jest częścią moreutils.
Wypróbuj to polecenie:
grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name | sponge file_name
źródło
brew install moreutils
.sudo apt-get install moreutils
w systemach opartych na Debianie.Zamiast tego użyj seda:
sed -i '/seg[0-9]\{1,\}\.[0-9]\{1\}/d' file_name
źródło
-i
jest tylko rozszerzeniem GNU, wystarczy zauważyć.-i ''
więc rozszerzenie nie jest to bezwzględnie obowiązkowe, ale-i
opcja wymaga jakiś argument.spróbuj tego prostego
grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name | tee file_name
Twój plik nie będzie tym razem pusty :), a wynik jest również drukowany na terminalu.
źródło
/dev/null
lub podobnych miejsc.Nie możesz użyć operatora przekierowania (
>
lub>>
) do tego samego pliku, ponieważ ma on wyższy priorytet i utworzy / obetnie plik przed wywołaniem polecenia. Aby tego uniknąć, należy użyć odpowiednich narzędzi, takich jaktee
,sponge
,sed -i
lub jakiekolwiek inne narzędzie, które może zapisywać wyniki do pliku (na przykładsort file -o file
).Zasadniczo przekierowywanie wejścia do tego samego oryginalnego pliku nie ma sensu i powinieneś do tego użyć odpowiednich edytorów lokalnych, na przykład edytora Ex (część Vima):
ex '+g/seg[0-9]\{1,\}\.[0-9]\{1\}/d' -scwq file_name
gdzie:
'+cmd'
/-c
- uruchom dowolną komendę Ex / Vimg/pattern/d
- usuń linie pasujące do wzorca za pomocą funkcji global (help :g
)-s
- tryb cichy (man ex
)-c wq
- wykonaj:write
i:quit
poleceniaMożna użyć
sed
, aby osiągnąć ten sam (jak już pokazano w innych odpowiedzi), jednak w miejscu (-i
) jest niestandardowym rozszerzeniem FreeBSD (może działać w różny sposób między Unix / Linux) iw zasadzie jest to s TREAM ed itor, a nie edytor plików . Zobacz: Czy tryb Ex ma jakieś praktyczne zastosowanie?źródło
Alternatywa dla jednego linera - ustaw zawartość pliku jako zmienną:
VAR=`cat file_name`; echo "$VAR"|grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' > file_name
źródło
Ponieważ to pytanie jest najlepszym wynikiem w wyszukiwarkach, oto jedna linijka oparta na https://serverfault.com/a/547331, która używa podpowłoki zamiast
sponge
(która często nie jest częścią instalacji waniliowej, takiej jak OS X) :echo "$(grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name)" > file_name
Ogólny przypadek to:
echo "$(cat file_name)" > file_name
Edytuj, powyższe rozwiązanie ma pewne zastrzeżenia:
printf '%s' <string>
powinno być używane zamiastecho <string>
, aby pliki zawierające-n
nie powodowały niepożądanego zachowania.x
do wyjścia i usunąć go na zewnątrz poprzez rozwinięcie parametrów zmiennej tymczasowej, takiej jak${v%x}
.$v
podbija wartość dowolnej istniejącej zmiennej$v
w bieżącym środowisku powłoki, więc powinniśmy zagnieździć całe wyrażenie w nawiasach, aby zachować poprzednią wartość.null
z wyniku. Sprawdziłem to, dzwoniącdd if=/dev/zero bs=1 count=1 >> file_name
i przeglądając szesnastkowo zcat file_name | xxd -p
. Aleecho $(cat file_name) | xxd -p
jest rozebrany. Tak więc , jak zauważył Lynch, nie należy używać tej odpowiedzi na plikach binarnych ani na niczym nie używającym znaków niedrukowalnych .Ogólne rozwiązanie (choć nieco wolniejsze, bardziej obciążające pamięć i nadal usuwające niedrukowalne znaki) to:
(v=$(cat file_name; printf x); printf '%s' ${v%x} > file_name)
Test z https://askubuntu.com/a/752451 :
printf "hello\nworld\n" > file_uniquely_named.txt && for ((i=0; i<1000; i++)); do (v=$(cat file_uniquely_named.txt; printf x); printf '%s' ${v%x} > file_uniquely_named.txt); done; cat file_uniquely_named.txt; rm file_uniquely_named.txt
Powinien wydrukować:
Natomiast wywołanie
cat file_uniquely_named.txt > file_uniquely_named.txt
aktualnej powłoki: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
Drukuje pusty ciąg.
Nie testowałem tego na dużych plikach (prawdopodobnie powyżej 2 lub 4 GB).
Zapożyczyłem tę odpowiedź od Hart Simha i kos .
źródło
cat
i umieszcza je jako pierwszy argumentecho
. Oczywiście zmienne niedrukowalne nie będą poprawnie wyprowadzane i uszkadzają dane. Nie próbuj przekierowywać pliku z powrotem do siebie, po prostu nie może być dobry.Istnieje również
ed
(jako alternatywa dlased -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
źródło
Możesz to zrobić za pomocą podstawiania procesów .
To trochę hack, ponieważ bash otwiera wszystkie potoki asynchronicznie i musimy to obejść, używając
sleep
YMMV.W twoim przykładzie:
grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name > >(sleep 1 && cat > file_name)
>(sleep 1 && cat > file_name)
tworzy plik tymczasowy, który otrzymuje dane wyjściowe od grepsleep 1
opóźnienia na sekundę, aby dać grepowi czas na przeanalizowanie pliku wejściowegocat > file_name
zapisuje dane wyjścioweźródło
Możesz używać slurp z POSIX Awk:
!/seg[0-9]\{1,\}\.[0-9]\{1\}/ { q = q ? q RS $0 : $0 } END { print q > ARGV[1] }
Przykład
źródło
Jest to bardzo możliwe, musisz tylko upewnić się, że zanim napiszesz wynik, zapisujesz go w innym pliku. Można to zrobić, usuwając plik po otwarciu do niego deskryptora pliku, ale przed zapisaniem do niego:
exec 3<file ; rm file; COMMAND <&3 >file ; exec 3>&-
Lub wiersz po wierszu, aby lepiej to zrozumieć:
exec 3<file # open a file descriptor reading 'file' rm file # remove file (but fd3 will still point to the removed file) COMMAND <&3 >file # run command, with the removed file as input exec 3>&- # close the file descriptor
Jest to nadal ryzykowne, ponieważ jeśli polecenie COMMAND nie zadziała poprawnie, utracisz zawartość pliku. Można to złagodzić, przywracając plik, jeśli COMMAND zwraca niezerowy kod zakończenia:
exec 3<file ; rm file; COMMAND <&3 >file || cat <&3 >file ; exec 3>&-
Możemy również zdefiniować funkcję powłoki, aby ułatwić korzystanie z:
# Usage: replace FILE COMMAND replace() { exec 3<$1 ; rm $1; ${@:2} <&3 >$1 || cat <&3 >$1 ; exec 3>&- }
Przykład:
$ echo aaa > test $ replace test tr a b $ cat test bbb
Pamiętaj również, że zachowa to pełną kopię oryginalnego pliku (do czasu zamknięcia trzeciego deskryptora pliku). Jeśli używasz Linuksa, a plik, na którym przetwarzasz, jest zbyt duży, aby zmieścić się dwukrotnie na dysku, możesz sprawdzić ten skrypt , który przekieruje plik do określonego polecenia blok po bloku, jednocześnie usuwając przydział już przetworzonego Bloki. Jak zawsze przeczytaj ostrzeżenia na stronie użytkowania.
źródło
Spróbuj tego
echo -e "AAA\nBBB\nCCC" > testfile cat testfile AAA BBB CCC echo "$(grep -v 'AAA' testfile)" > testfile cat testfile BBB CCC
źródło
Poniższe osiągną to samo, co
sponge
robi, bez koniecznościmoreutils
:Te
--random-source=/dev/zero
sztuczki partshuf
język robi jego rzecz bez jakiegokolwiek szuranie w ogóle, więc będzie buforować swój wkład bez zmieniania go.Jednak prawdą jest, że użycie pliku tymczasowego jest najlepsze ze względu na wydajność. Oto funkcja, którą napisałem, która zrobi to za Ciebie w uogólniony sposób:
# 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" }
źródło
Zwykle używam do tego programu tee :
grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name | tee file_name
Samodzielnie tworzy i usuwa plik tymczasowy.
źródło
tee
nie gwarantujemy działania. Zobacz askubuntu.com/a/752451/335781 .