Powłoka jedną linijkę, która ma być dołączona do pliku

132

To prawdopodobnie skomplikowane rozwiązanie .

Szukam prostego operatora, takiego jak „>>”, ale do poprzedzania.

Obawiam się, że nie istnieje. Muszę zrobić coś takiego

 mv myfile tmp
 cat myheader tmp> myfile

Coś mądrzejszego?

elmarco
źródło
Co jest nie tak mktemp? Zawsze możesz później wyczyścić plik tymczasowy ...
candu
Związane z: stackoverflow.com/questions/9533679/…
Anton Tarasenko

Odpowiedzi:

29

Poniższy hack był szybką, spontaniczną odpowiedzią, która zadziałała i otrzymała wiele pozytywnych głosów. Potem, gdy pytanie stawało się coraz bardziej popularne i mijało więcej czasu, oburzeni ludzie zaczęli zgłaszać, że to trochę działa, ale mogą się zdarzyć dziwne rzeczy, albo po prostu nie działa, więc przez jakiś czas było wściekle odrzucane. Taka zabawa.

Rozwiązanie wykorzystuje dokładną implementację deskryptorów plików w twoim systemie, a ponieważ implementacja różni się znacznie między nixami, jego sukces jest całkowicie zależny od systemu, definitywnie nieprzenośny i nie należy na nim polegać w przypadku niczego nawet mało ważnego.

Teraz, gdy to wszystko zniknęło, odpowiedź brzmiała:


Stworzenie innego deskryptora pliku dla pliku file ( exec 3<> yourfile), a następnie zapisywanie do this ( >&3) wydaje się przezwyciężyć dylemat odczytu / zapisu tego samego pliku. U mnie działa na plikach 600K z awk. Jednak próba tej samej sztuczki przy użyciu „kota” kończy się niepowodzeniem.

Przekazanie przedrostka jako zmiennej do awk ( -v TEXT="$text") rozwiązuje problem dosłownych cudzysłowów, który uniemożliwia wykonanie tej sztuczki z 'sed'.

#!/bin/bash
text="Hello world
What's up?"

exec 3<> yourfile && awk -v TEXT="$text" 'BEGIN {print TEXT}{print}' yourfile >&3
John Mee
źródło
3
OSTRZEŻENIE: sprawdź wyjście, aby upewnić się, że nic się nie zmieniło poza pierwszą linią. Użyłem tego rozwiązania i chociaż wydawało się, że działa, zauważyłem, że dodano kilka nowych linii. Nie rozumiem, skąd się one wzięły, więc nie mogę być pewien, czy to rozwiązanie naprawdę ma problem, ale uważaj.
conradlee
1
Zauważ, że w powyższym przykładzie basha podwójne cudzysłowy obejmują znak powrotu karetki, aby zademonstrować poprzedzanie wielu wierszy. Sprawdź, czy Twoja powłoka i edytor tekstu współpracują. \ r \ n przychodzi mi na myśl.
John Mee,
4
Używaj tego ostrożnie! Zobacz następujący komentarz, aby dowiedzieć się, dlaczego: stackoverflow.com/questions/54365/ ...
Alex
3
Jeśli teraz jest niewiarygodne, co powiesz na zaktualizowanie odpowiedzi o lepsze rozwiązanie?
zakdances
Hack jest przydatny tylko wtedy, gdy wiadomo dokładnie , dlaczego to działa, a zatem na podstawie tego, co dokładnie przestrzegać ograniczeń . Niezależnie od Twojego zastrzeżenia, Twój hack nie spełnia tych kryteriów („całkowicie zależy od systemu”, „wydaje się przezwyciężać”, „działa dla mnie”). Jeśli poważnie potraktujemy Twoje zastrzeżenie, nikt nie powinien używać Twojego hacka - i zgadzam się. Więc co nam zostało? Hałaśliwe rozproszenie uwagi. Widzę dwie możliwości: (a) usuń swoją odpowiedź lub (b) przekształć ją w przestrogę, która wyjaśnia, dlaczego - choć kuszące - takie podejście nie może działać ogólnie .
mklement0
103

To nadal używa pliku tymczasowego, ale przynajmniej jest w jednym wierszu:

echo "text" | cat - yourfile > /tmp/out && mv /tmp/out yourfile

Kredyt: BASH: Dołącz tekst / wiersze do pliku

Jason Navarrete
źródło
5
Co się dzieje -później cat?
makbol
Czy istnieje sposób na dodanie „tekstu” bez nowej linii po?
chishaku
@chishakuecho -n "text"
Sparhawk
3
Ostrzeżenie: jeśli yourfilejest dowiązaniem symbolicznym, to nie zrobi tego, co chcesz.
dshepherd
Czy odpowiedź jest inna niż ta: echo "text"> / tmp / out; cat yourfile >> / tmp / out; mv / tmp / out yourfile ??
Richard
31
echo '0a
your text here
.
w' | ed some_file

ed jest edytorem standardów! http://www.gnu.org/fun/jokes/ed.msg.html

puszysty
źródło
7
lub zamiast 0r header.file
zakodować na stałe
Ponieważ pytanie dotyczyło jednego liniowca:echo -e '0a\nyour text here\n.\nw' | ed some_file
raphinesse
20

John Mee: Twoja metoda nie ma gwarancji, że zadziała i prawdopodobnie zawiedzie, jeśli dodasz więcej niż 4096 bajtów rzeczy (przynajmniej tak się dzieje z gnu awk, ale przypuszczam, że inne implementacje będą miały podobne ograniczenia). W takim przypadku nie tylko zakończy się niepowodzeniem, ale wejdzie w nieskończoną pętlę, w której odczyta własne wyjście, dzięki czemu plik będzie rósł, dopóki cała dostępna przestrzeń nie zostanie wypełniona.

Wypróbuj sam:

exec 3<>myfile && awk 'BEGIN{for(i=1;i<=1100;i++)print i}{print}' myfile >&3

(ostrzeżenie: zabij go po chwili lub zapełni system plików)

Co więcej, edytowanie plików w ten sposób jest bardzo niebezpieczne i jest to bardzo zła rada, ponieważ jeśli coś się stanie podczas edycji pliku (awaria, pełny dysk), prawie na pewno pozostaniesz z plikiem w niespójnym stanie.

anonimowy
źródło
6
To prawdopodobnie powinien być komentarz, a nie osobna odpowiedź.
lkraav
10
@Ikraav: być może, ale „komentarz” jest bardzo długi i rozbudowany (i przydatny), nie będzie wyglądał dobrze i być może i tak nie będzie pasował do ograniczonej możliwości komentowania StackOverflow.
Hendy Irawan
18

Niemożliwe bez pliku tymczasowego, ale oto oneliner

{ echo foo; cat oldfile; } > newfile && mv newfile oldfile

Możesz użyć innych narzędzi, takich jak ed lub perl, aby zrobić to bez plików tymczasowych.

Vinko Vrsalovic
źródło
16

Warto zauważyć, że często dobrym pomysłem jest bezpieczne wygenerowanie pliku tymczasowego za pomocą narzędzia takiego jak mktemp , przynajmniej jeśli skrypt zostanie kiedykolwiek wykonany z uprawnieniami roota. Możesz na przykład wykonać następujące czynności (ponownie w bash):

(tmpfile=`mktemp` && { echo "prepended text" | cat - yourfile > $tmpfile && mv $tmpfile yourfile; } )
ehdr
źródło
15

Jeśli potrzebujesz tego na kontrolowanych komputerach, zainstaluj pakiet "moreutils" i użyj "sponge". Następnie możesz:

cat header myfile | sponge myfile
user2227573
źródło
1
Udało mi się to dobrze w połączeniu z odpowiedzią @Vinko Vrsalovic:{ echo "prepended text"; cat myfile } | sponge myfile
Jesse Hallett
13

Używając bash heredoc można uniknąć konieczności posiadania pliku tmp:

cat <<-EOF > myfile
  $(echo this is prepended)
  $(cat myfile)
EOF

To działa, ponieważ $ (cat myfile) jest sprawdzane podczas sprawdzania skryptu bash, przed wykonaniem cat z przekierowaniem.

Eric Woodruff
źródło
9

zakładając, że plik, który chcesz edytować, to my.txt

$cat my.txt    
this is the regular file

Plik, który chcesz dodać na początku, to nagłówek

$ cat header
this is the header

Upewnij się, że w pliku nagłówkowym znajduje się ostatnia pusta linia.
Teraz możesz go poprzedzić

$cat header <(cat my.txt) > my.txt

Skończysz z

$ cat my.txt
this is the header
this is the regular file

O ile wiem, działa to tylko w „bash”.

cb0
źródło
dlaczego to działa? Czy nawiasy powodują, że środkowy kot uruchamia się w oddzielnej powłoce i zrzuca cały plik na raz?
Catskul
Myślę, że <(cat my.txt) tworzy plik tymczasowy gdzieś w systemie plików. Ten plik jest następnie odczytywany, zanim oryginalny plik zostanie zmodyfikowany.
cb0
Ktoś kiedyś pokazał mi, co można zrobić ze składnią „<()”. Jednak nigdy nie dowiedziałem się, jak nazywa się ta metoda, ani nie mogłem znaleźć czegoś na stronie podręcznika bash. Jeśli ktoś wie więcej na ten temat, daj mi znać.
cb0
1
Nie działa dla mnie. Nie wiem dlaczego. Używam GNU bash w wersji 3.2.57 (1) -release (x86_64-apple-darwin14), używam OS X Yosemite. Skończyło się na dwóch liniach this is the headerw moim.txt. Nawet po zaktualizowaniu Bash 4.3.42(1)-release, otrzymuję ten sam wynik.
Kohányi Róbert
1
Bash nie tworzy pliku tymczasowego, tworzy FIFO (potok), do którego zapisuje polecenie w procesie substitution ( <(...)) , więc nie ma gwarancji, że my.txtzostanie odczytany w całości z góry , bez czego ta technika nie zadziała.
mklement0
9

Kiedy zaczynasz robić rzeczy, które stają się trudne w skrypcie powłoki, stanowczo sugerowałbym sprawdzenie przepisania skryptu w „odpowiednim” języku skryptowym (Python / Perl / Ruby / itp.)

Jeśli chodzi o dołączanie wiersza z wyprzedzeniem do pliku, nie można tego zrobić za pomocą pipingu, ponieważ gdy robisz coś podobnego cat blah.txt | grep something > blah.txt, nieumyślnie pusta plik. Istnieje małe polecenie narzędzia o nazwie, spongektóre możesz zainstalować (robisz to cat blah.txt | grep something | sponge blah.txti buforuje zawartość pliku, a następnie zapisuje ją do pliku). Jest podobny do pliku tymczasowego, ale nie musisz tego robić jawnie. ale powiedziałbym, że to „gorszy” wymóg niż, powiedzmy, Perl.

Może istnieć sposób na zrobienie tego przez awk lub podobny, ale jeśli musisz użyć skryptu powłoki, myślę, że plik tymczasowy jest zdecydowanie najłatwiejszym (/ jedynym?) Sposobem.

dbr
źródło
7

Jak sugeruje Daniel Velkov, użyj koszulki.
Dla mnie to proste, inteligentne rozwiązanie:

{ echo foo; cat bar; } | tee bar > /dev/null
Max Tsepkov
źródło
7

EDYCJA: To jest zepsute. Zobacz Dziwne zachowanie podczas poprzedzania pliku z kotem i tee

Obejście problemu z nadpisaniem polega na użyciu tee:

cat header main | tee main > /dev/null
Daniel
źródło
Przez chwilę wisiał. Zakończone słowem „tee: main: brak miejsca na urządzeniu”. główny zajmował kilka GB, chociaż oba oryginalne pliki tekstowe miały po kilka KB.
Marcos
3
Jeśli opublikowane przez Ciebie rozwiązanie jest zepsute (i faktycznie zapełnia dyski twarde użytkowników), wyświadcz społeczności przysługę i usuń swoją odpowiedź.
aioobe
1
Czy możesz wskazać mi wskazówkę, która mówi, że należy usunąć błędne odpowiedzi?
Daniel
3

Ten, którego używam. Ten pozwala określić kolejność, dodatkowe znaki itp. W taki sposób, w jaki lubisz:

echo -e "TEXTFIRSt\n$(< header)\n$(< my.txt)" > my.txt

PS: tylko nie działa, jeśli pliki zawierają tekst z odwrotnym ukośnikiem, ponieważ jest interpretowany jako znaki ucieczki

nemisj
źródło
3

Głównie dla zabawy / shell golfa, ale

ex -c '0r myheader|x' myfile

załatwi sprawę i nie ma żadnych potoków ani przekierowań. Oczywiście, vi / ex nie jest tak naprawdę do użytku nieinteraktywnego, więc vi na chwilę się wyświetli.

benjwadams
źródło
Najlepszy przepis z mojego punktu widzenia, ponieważ wykorzystuje obecne polecenie i robi to w prosty sposób.
Diego
2

Dlaczego po prostu nie użyć polecenia ed (jak już sugeruje tu fluffle)?

ed czyta cały plik do pamięci i automatycznie przeprowadza edycję pliku w miejscu!

Więc jeśli twój plik nie jest tak duży ...

# cf. "Editing files with the ed text editor from scripts.",
# http://wiki.bash-hackers.org/doku.php?id=howto:edit-ed

prepend() {
   printf '%s\n' H 1i "${1}" . wq | ed -s "${2}"
}

echo 'Hello, world!' > myfile
prepend 'line to prepend' myfile

Jeszcze innym obejściem byłoby użycie otwartych uchwytów plików, zgodnie z sugestią Jürgena Hötzela w Przekieruj dane wyjściowe z seda 's / c / d /' myFile do myFile

echo cat > manipulate.txt
exec 3<manipulate.txt
# Prevent open file from being truncated:
rm manipulate.txt
sed 's/cat/dog/' <&3 > manipulate.txt

Wszystko to można oczywiście umieścić w jednej linii.

torf
źródło
2

Wariant rozwiązania cb0 z „brakiem pliku tymczasowego” w celu dodania stałego tekstu na początku:

echo "text to prepend" | cat - file_to_be_modified | ( cat > file_to_be_modified ) 

Znowu polega to na wykonaniu podpowłoki - (..) - aby uniknąć odmowy cat odmowy posiadania tego samego pliku na wejściu i wyjściu.

Uwaga: podobało mi się to rozwiązanie. Jednak na moim komputerze Mac oryginalny plik został utracony (sądziłem, że nie powinien, ale tak się dzieje). Można to naprawić, pisząc rozwiązanie jako: echo "tekst na początek" | cat - file_to_be_modified | cat> tmp_file; mv tmp_file file_to_be_modified

Alberto A. Medina
źródło
1
Ja również uważam, że oryginalny plik został utracony. Ubuntu Lucid.
Jeż
2

Oto, co odkryłem:

echo -e "header \n$(cat file)" >file
Hammad Akhwand
źródło
1
To jest to samo, co wcześniejsza sugestia @nemisj Nie?
Jeż
2
sed -i -e '1rmyheader' -e '1{h;d}' -e '2{x;G}' myfile
słabe
źródło
2

OSTRZEŻENIE: wymaga to nieco więcej pracy, aby spełnić potrzeby PO.

Powinien istnieć sposób, aby podejście seda @shixilun działało pomimo jego obaw. Musi istnieć polecenie bash, aby uniknąć białych spacji podczas wczytywania pliku do ciągu zastępującego sed (np. Zamień znaki nowej linii na '\ n'. Polecenia powłoki visi catmogą obsługiwać znaki niedrukowalne, ale nie białe spacje, więc to nie rozwiąże operacji problem:

sed -i -e "1s/^/$(cat file_with_header.txt)/" file_to_be_prepended.txt

kończy się niepowodzeniem ze względu na nieprzetworzone znaki nowej linii w skrypcie zastępczym, które należy poprzedzić znakiem kontynuacji linii () i być może poprzedzonym znakiem &, aby zachować powłokę i seda w dobrym stanie, jak ta odpowiedź SO

sed ma ograniczenie rozmiaru do 40 KB dla nieglobalnych poleceń wyszukiwania i zamiany (bez końcowego / g po wzorcu), więc prawdopodobnie pozwoliłoby uniknąć przerażających problemów z przepełnieniem bufora w awk, przed którymi ostrzegał anonimowy.

płyty
źródło
2
sed -i -e "1s/^/new first line\n/" old_file.txt
shixilun
źródło
dokładnie to, czego szukałem, ale rzeczywiście nie jest to właściwa odpowiedź na pytanie
masterxilo
2

Za pomocą $ (polecenie) możesz zapisać wynik polecenia do zmiennej. Więc zrobiłem to w trzech poleceniach w jednej linii i bez pliku tymczasowego.

originalContent=$(cat targetfile) && echo "text to prepend" > targetfile && echo "$originalContent" >> targetfile
JuSchu
źródło
1

Jeśli piszesz w BASH, możesz po prostu wydać:

cat - twój plik / tmp / out && mv / tmp / out twój plik

To jest właściwie w złożonym przykładzie, który sam zadałeś w swoim własnym pytaniu.

Tim Kennedy
źródło
Redaktor zasugerował zmianę tego na cat - yourfile <<<"text" > /tmp/out && mv /tmp/out yourfile, ale to wystarczająco różni się od mojej odpowiedzi, że powinna to być jego własna odpowiedź.
Tim Kennedy,
1

Jeśli masz duży plik (w moim przypadku kilkaset kilobajtów) i masz dostęp do Pythona, jest to znacznie szybsze niż catrozwiązania potokowe:

python -c 'f = "filename"; t = open(f).read(); open(f, "w").write("text to prepend " + t)'

crizCraig
źródło
1

Rozwiązanie z printf:

new_line='the line you want to add'
target_file='/file you/want to/write to'

printf "%s\n$(cat ${target_file})" "${new_line}" > "${target_file}"

Możesz też:

printf "${new_line}\n$(cat ${target_file})" > "${target_file}"

Ale w takim przypadku musisz być pewien, że %nigdzie nie ma , w tym zawartości pliku docelowego, ponieważ można to zinterpretować i zepsuć wyniki.

user137369
źródło
2
Wydaje się, że wysadza (i obcina twój plik!), Jeśli masz w pliku docelowym coś, co printf interpretuje jako opcję formatowania.
Alan H.,
echowydaje się bezpieczniejszą opcją. echo "my new line\n$(cat my/file.txt)" > my/file.txt
Alan H.,
@AlanH. Ostrzegam przed niebezpieczeństwami w odpowiedzi.
user137369
Właściwie ostrzegałeś tylko o procentach w ${new_line}pliku docelowym, a nie o pliku docelowym
Alan H.
Czy mógłbyś wyrazić się bardziej wprost? To naprawdę duże zastrzeżenie IMO. „Gdziekolwiek” nie wyjaśnia tego jasno.
Alan H.,
1

Możesz użyć wiersza poleceń perla:

perl -i -0777 -pe 's/^/my_header/' tmp

Gdzie -i utworzy bezpośrednią zamianę pliku, a -0777 siorbi cały plik i dopasuje ^ tylko początek. -pe wypisze wszystkie linie

Lub jeśli my_header jest plikiem:

perl -i -0777 -pe 's/^/`cat my_header`/e' tmp

Gdzie / e pozwoli na ocenę kodu w zamianie.

user5704481
źródło
0
current=`cat my_file` && echo 'my_string' > my_file && echo $current >> my_file

gdzie „my_file” to plik, do którego należy dołączyć „my_string”.

Vinyll
źródło
O ile nie chcesz, aby jakakolwiek „magia” zaczęła się dziać od czasu do czasu, należy cytować podstawienia zmiennych.
luis lavaire
0

Najbardziej podoba mi się podejście ed @ fluffle . W końcu przełączniki wiersza poleceń dowolnego narzędzia a polecenia edytora skryptowego są tutaj zasadniczo takie same; nie widząc „czystości” rozwiązania edytora skryptów jako mniejszego lub czegoś innego.

Oto moja jedna linijka dołączona do .git/hooks/prepare-commit-msgpliku w repozytorium w .gitmessagecelu zatwierdzenia wiadomości:

echo -e "1r $PWD/.gitmessage\n.\nw" | ed -s "$1"

Przykład .gitmessage:

# Commit message formatting samples:
#       runlevels: boot +consolekit -zfs-fuse
#

Robię 1rzamiast tego 0r, ponieważ pozostawi to pustą linię gotową do zapisu na górze pliku z oryginalnego szablonu. Nie umieszczaj pustej linii na swoim .gitmessage, skończysz z dwoma pustymi liniami. -sblokuje wyświetlanie informacji diagnostycznych przez ed.

W związku z przechodzeniem przez to odkryłem, że dla vim-buffów dobrze jest mieć również:

[core]
        editor = vim -c ':normal gg'
lkraav
źródło
0

zmienne, ftw?

NEWFILE=$(echo deb http://mirror.csesoc.unsw.edu.au/ubuntu/ $(lsb_release -cs) main universe restricted multiverse && cat /etc/apt/sources.list)
echo "$NEWFILE" | sudo tee /etc/apt/sources.list
Jayen
źródło
0

Myślę, że to najczystsza odmiana ed:

cat myheader | { echo '0a'; cat ; echo -e ".\nw";} | ed myfile

jako funkcja:

function prepend() { { echo '0a'; cat ; echo -e ".\nw";} | ed $1; }

cat myheader | prepend myfile
Dave Butler
źródło
0

IMHO nie ma rozwiązania powłoki (i nigdy nie będzie), które działałoby konsekwentnie i niezawodnie niezależnie od rozmiarów dwóch plików myheaderi myfile. Powodem jest to, że jeśli chcesz to zrobić bez powracania do pliku tymczasowego (i bez pozwalania powłoce na ciche powtarzanie się do pliku tymczasowego, np. Przez konstrukcje takie jak exec 3<>myfile, rurociąg do teeitp.

"Prawdziwe" rozwiązanie, którego szukasz, musi majstrować przy systemie plików, więc nie jest dostępne w przestrzeni użytkownika i byłoby zależne od platformy: prosisz o zmodyfikowanie wskaźnika systemu plików używanego przez myfiledo bieżącej wartości wskaźnika systemu plików for myheaderi zamień w systemie plików EOFof myheaderpołączonym odnośnikiem do bieżącego adresu systemu plików wskazywanego przez myfile. Nie jest to trywialne i oczywiście nie może tego zrobić użytkownik niebędący superużytkownikiem, a prawdopodobnie też superużytkownik ... Graj z i-węzłami itp.

Możesz to mniej więcej udawać, używając urządzeń pętlowych. Zobacz na przykład ten wątek SO .

jaybee
źródło