Vraag Linux: hoe gebruik je een bestand tegelijkertijd als invoer en uitvoer?


Ik heb net het volgende in bash uitgevoerd:

uniq .bash_history > .bash_history

en mijn geschiedenisbestand is helemaal leeg.

Ik denk dat ik een manier nodig heb om het hele bestand te lezen voordat ik het schrijf. Hoe is dat gebeurd?

PS: Ik dacht overduidelijk aan het gebruik van een tijdelijk bestand, maar ik ben op zoek naar een elegantere oplossing.


45
2018-04-24 18:32


oorsprong


Het komt omdat de bestanden van rechts naar links worden geopend. Zie ook stackoverflow.com/questions/146435/... - kaerast
U moet de uitvoer naar een nieuw bestand in dezelfde map schrijven en de naam ervan wijzigen boven aan het oude bestand. Elke andere benadering riskeert uw gegevens te verliezen als deze halverwege wordt onderbroken. Sommige hulpmiddelen kunnen deze stap voor u verbergen. - kasperd
Of, bash zal geen opeenvolgende dupes in zijn geschiedenis plaatsen als je HISTCONTROL instelt om ignoredups op te nemen; zie de manpage. - dave_thompson_085


antwoorden:


Ik raad aan om te gebruiken sponge van moreutils. Van de manpage:

DESCRIPTION
  sponge  reads  standard  input  and writes it out to the specified file. Unlike
  a shell redirect, sponge soaks up all its input before opening the output file.
  This allows for constructing pipelines that read from and write to the same 
  file.

Om dit op uw probleem toe te passen, probeert u:

uniq .bash_history | sponge .bash_history

41
2018-04-24 22:46



Het is als een kat, maar met zuigkracht: D - MilliaLover


Ik wilde gewoon een ander antwoord bijdragen dat eenvoudig is en geen spons gebruikt (omdat het vaak niet is opgenomen in lichtgewicht omgevingen).

echo "$(uniq .bash_history)" > .bash_history

zou het gewenste resultaat moeten hebben. De subshell wordt uitgevoerd voordat .bash_history wordt geopend om te schrijven. Zoals uitgelegd in het antwoord van Phil P, is tegen de tijd dat .bash_history in de oorspronkelijke opdracht wordt gelezen, deze al afgekapt door de operator '>'.


60
2017-10-20 08:46



Ik ben normaal gesproken geen fan van antwoorden die een oude vraag opsommen die al een geldig, geaccepteerd antwoord heeft - maar dit is elegant, goed geschreven en een sterk argument voor de noodzaak ervan (lichtgewicht omgevingen); voor mij voegt het echt iets toe aan de bestaande reeks antwoorden. Welkom bij SF, Hart (je bent hier al een maand, maar ik denk dat dit je eerste inhoudelijke posting is). Ik hoop meer antwoorden van jou te lezen zoals deze! - MadHatter
Dit is de beste oplossing. Ik moest een subshell gebruiken $() in plaats van backticks vanwege enkele ontsnappingsproblemen. - CMCDragonkai
Ik vraag me af of deze oplossing kan worden geschaald naar grote bestanden, bijvoorbeeld ... 20 of 50 GB. - Amit Naidu
Dit zou echt het accept antwoord moeten zijn. - maxywb
Daar ben ik het mee eens. Ik heb het geaccepteerde antwoord gemarkeerd, dus hopelijk zal iemand het overnemen. - Ced


Het probleem is dat uw shell de opdrachtpijplijn instelt voordat de opdrachten worden uitgevoerd. Het is geen kwestie van "input en output", het is dat de inhoud van het bestand al weg is voordat uniq zelfs wordt uitgevoerd. Het gaat ongeveer als volgt:

  1. De schaal opent de > uitvoerbestand om te schrijven, af te kappen
  2. De shell wordt zodanig ingesteld dat file-descriptor 1 (voor stdout) voor die uitvoer wordt gebruikt
  3. De shell voert uniq uit, misschien iets als execlp ("uniq", "uniq", ".bash_history", NULL)
  4. uniq wordt uitgevoerd, opent .bash_history en vindt daar niets

Er zijn verschillende oplossingen, waaronder de interne bewerking en het tijdelijke bestandsgebruik dat anderen vermelden, maar de sleutel is om het probleem te begrijpen, wat er eigenlijk mis gaat en waarom.


11
2018-04-24 23:36





gebruik spons van moreutils

uniq .bash_history | sponge .bash_history

6
2018-04-24 22:46





Nog een truc om dit te doen, zonder te gebruiken sponge, is het volgende commando:

{ rm .bash_history && uniq > .bash_history; } < .bash_history

Dit is een van de cheats beschreven in het uitstekende artikel "In-place" bewerking van bestanden op backreference.org.

Het opent in principe het te lezen bestand en verwijdert het vervolgens. Het is echter niet echt verwijderd: er staat een geopende bestandsdescriptor naar, en zolang dat open blijft, is het bestand nog steeds in de buurt. Vervolgens wordt een nieuw bestand met dezelfde naam gemaakt en worden de unieke regels ernaar geschreven.

Nadeel van deze oplossing: als uniq mislukt om een ​​of andere reden, je geschiedenis zal verdwenen zijn.


6
2017-11-26 15:49





Deze sed script verwijdert aangrenzende duplicaten. Met de -i optie, het doet de wijziging op zijn plaats. Het komt van de sed  info het dossier:

sed -i 'h;:b;$b;N;/^\(.*\)\n\1$/ {g;bb};$b;P;D' .bash_history

3
2018-04-24 19:39



sed gebruikt nog steeds het tijdelijke bestand, heeft een antwoord toegevoegd strace illustratie (niet dat het er echt toe doet) :-) - Kyle Brandt♦
@Kyle: waar, maar "uit het oog, uit het hart". Persoonlijk zou ik het expliciete tijdelijke bestand gebruiken sinds zoiets process input > tmp && mv tmp input is veel eenvoudiger en leesbaarder dan het gebruik sed bedrog gewoon om een ​​tijdelijk bestand te vermijden en het zal mijn origineel niet overschrijven als het mislukt (ik weet niet of sed -i faalt elegant - ik zou echter denken dat het dat wel zou doen). Trouwens, er zijn veel dingen die je kunt doen met de output-to-temp-bestand methode die niet op zijn plaats kan worden gedaan zonder dat er iets meer betrokken is dan dit sed script. Ik weet dat je dit allemaal weet, maar het kan sommige toeschouwers ten goede komen. - Dennis Williamson


Als een interessante lekkernij gebruikt sed ook een tijdelijk bestand (dit doet het gewoon voor jou):

$ strace sed -i 's/foo/bar/g' foo    
open("foo", O_RDONLY|O_LARGEFILE)       = 3
...
open("./sedPmPv9z", O_RDWR|O_CREAT|O_EXCL|O_LARGEFILE, 0600) = 4
...
read(3, "foo\n"..., 4096)               = 4
write(4, "bar\n"..., 4)                 = 4
read(3, ""..., 4096)                    = 0
close(3)                                = 0
close(4)                                = 0
rename("./sedPmPv9z", "foo")            = 0
close(1)                                = 0
close(2)                                = 0

Omschrijving:
Het tijdelijke bestand ./sedPmPv9z wordt fd 4 en de foo bestanden worden fd 3. De leesbewerkingen staan ​​op fd 3 en de schrijft op fd 4 (het tijdelijke bestand). Het foo-bestand wordt dan overschreven met het tijdelijke bestand in de naam van de oproep.


3
2018-04-25 01:12





Een tijdelijk bestand is zo ongeveer, tenzij het betreffende commando ter plaatse ondersteuning biedt bij het bewerken (uniq niet seds doen (sed -i)).


0
2018-04-24 18:45





U kunt Vim in Ex-modus gebruiken:

ex -sc '%!uniq' -cx .bash_history
  1. %selecteer alle lijnen

  2. ! voer opdracht uit

  3. x opslaan en sluiten


0
2018-04-10 16:41