En stor (upp till 2 GiB) textfil av mig innehåller cirka 100 exakta dubbletter av varje rad i den (meningslöst i mitt fall, eftersom filen är en CSV-liknande datatabell.
Vad jag behöver är att ta bort alla repetitioner medan (helst, men detta kan offras för en betydande prestationsförbättring) att behålla den ursprungliga sekvensordningen. I resultatet ska varje rad vara unik. Om det fanns 100 lika rader (vanligtvis är dubbletterna spridda över filen och det kommer inte att vara grannar) är det bara en av den typen kvar.
Jag har skrivit ett program i Scala (anser det Java om du inte vet om Scala) för att implementera detta. Men kanske finns det snabbare C-skrivna inbyggda verktyg som kan göra detta snabbare?
UPPDATERING: awk "!seen[$0]++" filename
-lösningen verkade fungera bra för mig så länge filerna var nära 2 GiB eller mindre men nu när jag ska städa upp en 8 GiB-fil fungerar det inte längre. Det verkar ta oändlighet på en Mac med 4 GiB RAM och en 64-bitars Windows 7 PC med 4 GiB RAM och 6 GiB swap tar slut på minne. Och jag känner mig inte entusiastisk över att testa det på Linux med 4 GiB RAM med tanke på den här upplevelsen.
Kommentarer
Svar
En awk
-lösning sett på #bash (Freenode):
awk "!seen[$0]++" filename
Kommentarer
- Prövade precis detta på en 2G-fil och det tog tre minuter på min bärbara dator. Inte dåligt. Jag försökte också uniq filnamn | awk ’! sett [$ 0] ++ ’, men det var inte ’ t snabbare.
- @HashWizard: det här kommandot sorteras inte, men eliminerar varje nästa förekomst av samma rad
- Undrar du hur det här kommandot fungerar? – Se här: unix.stackexchange.com/questions/159695/how-does-awk-a0-work
- @MaxWilliams ja , det fungerar är att de är slumpmässigt fördelade.
- bevara nya rader eller rader med mellanslag
awk '/^\s*?$/||!seen[$0]++'
Svar
Det finns en enkel (vilket inte är självklart) metod med standardverktyg som inte kräver ett stort minne förutom att köra sort
, som i de flesta implementeringar har specifika optimeringar för stora filer (en bra extern sorteringsalgoritm). En fördel med denna metod är att den bara slingrar över alla rader i specialverktyg, aldrig inuti tolkade språk.
<input nl -b a -s : | # number the lines sort -t : -k 2 -u | # sort and uniquify ignoring the line numbers sort -t : -k 1n | # sort according to the line numbers cut -d : -f 2- >output # remove the line numbers
Om alla rader börjar med en icke-blankstegstecken kan du avstå från några av alternativen:
<input nl | sort -k 2 -u | sort -k 1n | cut -f 2- >output
För en stor mängd duplicering, en metod som bara kräver lagring av en enda kopia av varje rad i minnet kommer att fungera bättre. Med viss tolkning över huvudet finns det ett mycket kortfattat awk-skript för det (redan postat av enzotib ):
<input awk "!seen[$0]++"
Mindre kortfattat: !seen[$0] {print} {seen[$0] += 1}
, dvs skriva ut den aktuella raden om den inte har setts ännu, öka sedan seen
räknare för den här raden (oinitialiserade variabler eller arrayelement har det numeriska värdet 0).
För långa rader kan du spara minne genom att bara hålla en icke-falsk kontrollsumma (t.ex. en kryptografisk sammandragning) av varje rad . Om du till exempel använder SHA-1 behöver du bara 20 byte plus en konstant overhead per rad. Men datainsamling är ganska långsam; den här metoden vinner bara om du har en snabb CPU (särskilt en med en hårdvaruaccelerator för att beräkna smälten) och inte mycket minne i förhållande till filens storlek och tillräckligt långa rader. Inget grundläggande verktyg låter dig beräkna en kontrollsumma för varje rad; du måste bära tolkningen av Perl / Python / Ruby / … eller skriva ett dedikerat sammanställt program.
<input perl -MDigest::MD5 -ne "$seen{Digest::MD5::md5($_)}++ or print" >output
Kommentarer
- @Gilles Baserat på din förklaring av
awk '!seen[$0]++'
, betyder det att om awk ser två dubbla rader kommer det att behålla den alltid första och ignorera alla efterföljande? (Eller behåller den sista?) - @ user779159 Den behåller den första: varje inmatningsrad skrivs antingen ut omedelbart (första förekomst) eller inte alls (upprepad förekomst).
- Men hur jämför det med sort -u …?
- @HashWizard En vanlig
sort -u
ändrar ordningen.Mitt svar visar lösningar som bevarar ordningen (ordningen för de första händelserna, för att vara exakt). - @ Gilles skulle du säga att det är snabbare än sortering -u för stora filer (10G) med 50% dubbletter ?
Svar
sort -u big-csv-file.csv > duplicates-removed.csv
Observera att utdatafilen kommer att sorteras.
Kommentarer
- Inte lika snabbt som kommandot
awk
i andra svar, men konceptuellt enkelt! - @Johann Jag gör det ganska ofta på filer med hundratusentals (till och med miljoner) korta strängar som avslutats med nya rader. Jag får resultaten ganska snabbt för de experiment jag gör. Det kan vara viktigare om det används i skript som körs om och om igen, tidsbesparingar kan vara betydande.
- Använd
sort -u
för att ta bort dubbletter under sorteringen, snarare än efter. (Och sparar minnesbandbredd) som rör det till ett annat program). Det här är bara bättre änawk
-versionen om du också vill sortera utdata. (OP för den här frågan vill att hans ursprungliga beställning ska evaras , så det här är ett bra svar för ett lite annorlunda användningsfall.) en 5,5 miljoner radfil (totalt 1,8 GB). Lysande.
Svar
Förutsatt att du har råd att behålla lika mycket som den av duplicerade filen i minnet ( om dina data verkligen dupliceras med en faktor 100, det borde vara ungefär 20MiB + overhead), kan du göra det mycket enkelt med Perl.
$ perl -ne "print unless $dup{$_}++;" input_file > output_file
Detta bevarar ordningen också.
Du kan extrahera antalet förekomster av varje rad från %dup
hash om du så önskar, som en extra gratis bonus.
Om du föredrar awk
, bör detta också göra det (samma logik som perlversionen, samma ordning, samma data som samlats in i dup
-variabel):
$ awk "{if (++dup[$0] == 1) print $0;}" input_file > output_file
Kommentarer
- Det här är för bra @Mat, jag var på väg att slurpa filen, lol ;-).
- Väntar nu på @ManAtWork för hans sed och awk magiska vävning också 🙂
- fantastiskt igen för awk-tipset: – )
- Är det möjligt att ändra perl-skriptet till att bara ta bort e duplicera intilliggande rader?
- @dumbledad:
uniq
gör det helt av sig själv
Svar
Eftersom inget annat svar tillhandahålls support på plats, här är ett:
gawk -i inplace "!a[$0]++" file
Kommentarer
- Bevarar detta beställningen? Förresten, det här fungerade inte för mig. Min version är:
GNU Awk 4.0.2
- @Leonid ja, det gör det. Den skriver ut den första förekomsten av en unik linje. Inplace-supporten introducerades först i version 4.1, som släpptes 2013.
- Detta borde vara svaret. Det ’ raderar faktiskt den duplicerade strängen i den befintliga eller nuvarande filen där det översta svaret och de flesta av svaren här bara skriver ut uniq / duplicerade strängar och gör ingenting och vi måste skapa en annan utgång för att lagra resultatet.
Svar
Du kan använda uniq
http://www.computerhope.com/unix/uuniq.htm
uniq
rapporterar eller filtrerar bort upprepade rader i en fil.
Kommentarer
- När du svarar är det bättre att ge någon förklaring till varför ditt svar är det. Så, hur skiljer sig detta svar från flera av de tidigare svaren?
- Från uniq man-sidan: Obs:
'uniq' does not detect repeated lines unless they are adjacent.
Så du måste först sortera det och lösa ordningen på de icke dubbla raderna.
Svar
Python One-liners:
python -c "import sys; lines = sys.stdin.readlines(); print "".join(sorted(set(lines)))" < InputFile
Kommentarer
- detta gör att hela filen slurps i minnet och kanske inte passar bra för OP ’ -problemet. Inte garanterat att jag behåller ordningen
- Tack för förslaget, jag ’ har bara lärt mig python .. försökte bara detta för inlärningssyfte ..:)
- Här ’ s en Python 2.7-version som inte är en enfodrad men (kortfattad) returnerar unika rader som bevarar ordning utan att antingen ladda hela filen i minnet eller skapa en enda gigantisk sträng att mata ut för att skriva ut
- Tack @ 1_CR Jag har något att lära idag 🙂
OrderedDict
Svar
Inget av svaren här fungerade för mig på min Mac så jag skrev en enkel python manus som fungerar för mig. Jag ignorerar ledande / efterföljande tomrum och bryr mig inte om minneskonsumtion.
import sys inputfile = sys.argv[1] outputfile = sys.argv[2] with open(inputfile) as f: content = f.readlines() content = [x.strip() for x in content] my_list = list(set(content)) with open(outputfile, "w") as output: for item in my_list: output.write("%s\n" % item)
Spara ovanstående till unikt.py och kör så här:
python unique.py inputfile.txt outputfile.txt
Svar
LÖSNING UTAN UNDERHÅLLNING AV DEN ORIGINALA SEKVENSORDNINGEN
Jag gjorde det med följande kodbit.
sort duplicates.txt | uniq > noDuplicates.txt
Kommandot sort
sorterar raderna alfabetiskt och kommandot uniq
tar bort dubbletterna.
OBS: Varför vi sorterade raderna först är att uniq
upptäcker inte dubbla rader såvida de inte ligger intill varandra.
Kommentarer
- Frågan frågar efter en metod (helst ) som bibehåller inmatningsordningen; kan du redigera ditt svar för att ta itu med det? Observera att det finns befintliga svar med
sort
som bibehåller inmatningsordningen och ett svar medsort
utan att upprätthålla inmatningsordningen men på ett mer effektivt sätt än att leda tilluniq
. - @StephenKitt Edited. Jag inspekterade andra svar men kunde inte ’ bara hitta något med grundläggande kommandon. Tack för din feedback.
- Jag gav dig en länk till ett svar med endast grundläggande kommandon, faktiskt bara ett kommando,
sort -u
(som är en del av POSIX ) ;-). - @StephenKitt Jag såg svaret. Gruvan är också ett sätt att hantera problemet. Vad vill du att jag ska göra mer? Ska jag radera svaret?
- Nej, ta inte bort ditt svar. Jag ville bara se till att du var medveten om det andra svaret, med tanke på att du sa att du ”inte kunde ’ inte hitta någonting bara med grundläggande kommandon.
Svar
Med bash 4, en ren bash-lösning som utnyttjar associerande matriser kan användas. Här är ett exempel
unset llist; declare -A llist; while read -r line; do if [[ ${llist[$line]} ]]; then continue else printf "%s\n" "$line" llist[$line]="x" fi done < file.txt
Kommentarer
- Don ’ t använd
read
loopar för att bearbeta stora textfiler. bash måste läsa en-byte-på-en-gång för att undvika att överskrida en ny linje. Bash är inte heller mycket snabb vid textbehandling i allmänhet jämfört med awk. Om du använder detta kommerread -ra
att undvika att ta tillbaka snedstreck i din inmatning. Glöm inte att ’ t glömma attunset llist
efter slingan, om du lägger detta i en skalfunktion eller använd den interaktivt. - @PeterCordes, eller så kan du bara ha refererat till detta 🙂
sort -u
förmodligen att vara snabbare.