Komut satırı aracı, bir dosyadaki tüm satırların çift olarak genişletilmesine “cat”


13

Aşağıdaki gibi görünen bir dosyam var (örnek.txt olarak adlandırın):

Row1,10
Row2,20
Row3,30
Row4,40

Ben esas olarak dört satırın (yani toplam 16 ile bitmelidir) ikili kombinasyonu olan bu dosyadan bir akış üzerinde çalışmak istiyorum. Örneğin, çıktı olduğu bir akış (yani verimli) komut arıyorum:

Row1,10 Row1,10
Row1,10 Row2,20
Row1,10 Row3,30
Row1,10 Row4,40
Row2,20 Row1,10
Row1,20 Row2,20
...
Row4,40 Row4,40

Benim kullanım durumum, bu çift kombinasyon hakkında bazı metrik hesaplamak için bu çıkışı başka bir komut (awk gibi) akışı istiyorum.

Ben awk bunu yapmak için bir yol var ama benim endişe END {} blok kullanımı benim çıktı önce temelde tüm dosyayı bellekte saklamak anlamına gelir. Örnek kod:

awk '{arr[$1]=$1} END{for (a in arr){ for (a2 in arr) { print arr[a] " " arr[a2]}}}' samples/rows.txt 
Row3,30 Row3,30
Row3,30 Row4,40
Row3,30 Row1,10
Row3,30 Row2,20
Row4,40 Row3,30
Row4,40 Row4,40
Row4,40 Row1,10
Row4,40 Row2,20
Row1,10 Row3,30
Row1,10 Row4,40
Row1,10 Row1,10
Row1,10 Row2,20
Row2,20 Row3,30
Row2,20 Row4,40
Row2,20 Row1,10
Row2,20 Row2,20

Dosyayı belleğe depolamak ve sonra END bloğuna çıktı vermek zorunda kalmadan bunu yapmanın etkili bir akış yolu var mı?


1
Diğer dosyanın ikinci satırı için çıktı üretmeye başlamadan önce daima bir dosyayı sonuna kadar okumanız gerekir. Aktarabileceğiniz diğer dosya.
reinierpost

Yanıtlar:


12

Tüm dosyayı bir dizide saklamak zorunda kalmamak için awk içinde nasıl yapılacağı aşağıda açıklanmıştır. Bu temelde terdon'larla aynı algoritmadır.

İsterseniz, komut satırına birden fazla dosya adı bile verebilirsiniz ve her dosyayı bağımsız olarak işleyerek sonuçları bir araya getirecektir.

#!/usr/bin/awk -f

#Cartesian product of records

{
    file = FILENAME
    while ((getline line <file) > 0)
        print $0, line
    close(file)
}

Sistemimde bu, terdon'un perl çözeltisinin yaklaşık 2 / 3'ünde çalışır.


1
Teşekkürler! Bu soruna tüm çözümler fantastik ama 1 ile bu bir gidiş gidiş sona erdi) ve basitlik 2). Teşekkürler!
Tom Hayden

1
Beğendiğine sevindim Tom. Bugünlerde çoğunlukla Python'da programlama eğilimindeyim, ancak satırlar ve dosyalar üzerindeki yerleşik döngüleri nedeniyle satır satır metin işleme için hala awk'yi seviyorum. Ve genellikle Python'dan daha hızlıdır.
PM 2Ring

7

Bunun bellekte yapmaktan daha iyi olmadığından emin değilim, ancak dosyasındaki her satır için dolgusunu ve giriş çizgileriyle eski alanı değiştiren bir borunun diğer tarafındaki sızıntısını ortaya sedçıkaran rbir H...

cat <<\IN >/tmp/tmp
Row1,10
Row2,20
Row3,30
Row4,40
IN

</tmp/tmp sed -e 'i\
' -e 'r /tmp/tmp' | 
sed -n '/./!n;h;N;/\n$/D;G;s/\n/ /;P;D'

ÇIKTI

Row1,10 Row1,10
Row1,10 Row2,20
Row1,10 Row3,30
Row1,10 Row4,40
Row2,20 Row1,10
Row2,20 Row2,20
Row2,20 Row3,30
Row2,20 Row4,40
Row3,30 Row1,10
Row3,30 Row2,20
Row3,30 Row3,30
Row3,30 Row4,40
Row4,40 Row1,10
Row4,40 Row2,20
Row4,40 Row3,30
Row4,40 Row4,40

Bunu başka bir şekilde yaptım. Bu saklamak yapar bazı : Bir dize gibi saklar - bellekte

"$1" -

... dosyadaki her satır için.

pairs(){ [ -e "$1" ] || return
    set -- "$1" "$(IFS=0 n=
        case "${0%sh*}" in (ya|*s) n=-1;; (mk|po) n=+1;;esac
        printf '"$1" - %s' $(printf "%.$(($(wc -l <"$1")$n))d" 0))"
    eval "cat -- $2 </dev/null | paste -d ' \n' -- $2"
}

Çok hızlı. Bu cat, dosyada satırlar olduğu kadar çok dosyadır |pipe. Borunun diğer tarafında bu giriş, dosyada satırlar olduğu kadar dosyanın kendisi ile birleştirilir.

case- malzeme sadece taşınabilirlik içindir yashve zshsüre, bölünmeye hem eklenti bir unsuru mkshve poshhem kaybetmek biri. ksh, dash, busyboxVe bashtarafından basılan olarak sıfır var gibi birçok alanda tam olarak dışarı bütün bölünmüş printf. Yukarıda yazıldığı gibi, makinemde yukarıda belirtilen mermilerin her biri için aynı sonuçları verir.

Dosya çok uzunsa, $ARGMAXçok fazla argümanla ilgili sorunlar olabilir , bu durumda da tanıtmanız xargsveya benzer olmanız gerekir .

Çıktı aynı olmadan önce kullanılan aynı girdi göz önüne alındığında. Ama daha büyük olsaydım ...

seq 10 10 10000 | nl -s, >/tmp/tmp

Bu, daha önce kullandığımla neredeyse aynı olan bir dosya oluşturur (sans 'Row') - ancak 1000 satırda. Ne kadar hızlı olduğunu kendiniz görebilirsiniz:

time pairs /tmp/tmp |wc -l

1000000
pairs /tmp/tmp  0.20s user 0.07s system 110% cpu 0.239 total
wc -l  0.05s user 0.03s system 32% cpu 0.238 total

1000 satırda, mermiler arasında performansta küçük bir değişiklik var - bashher zaman en yavaş olanı - ama yine de yaptıkları tek iş, arg dizesini (1000 kopya filename -) oluşturmak olduğu için, etki minimumdur. zshYukarıdaki gibi - ve arasındaki performans farkı bashburada saniyenin 100.'sidir.

İşte herhangi bir uzunlukta bir dosya için çalışması gereken başka bir sürüm:

pairs2()( [ -e "$1" ] || exit
    rpt() until [ "$((n+=1))" -gt "$1" ]
          do printf %s\\n "$2"
          done
    [ -n "${1##*/*}" ] || cd -P -- "${1%/*}" || exit
    : & set -- "$1" "/tmp/pairs$!.ln" "$(wc -l <"$1")"
    ln -s "$PWD/${1##*/}" "$2" || exit
    n=0 rpt "$3" "$2" | xargs cat | { exec 3<&0
    n=0 rpt "$3" p | sed -nf - "$2" | paste - /dev/fd/3
    }; rm "$2"
)

İlk argümanına /tmpyarı rastgele bir adla yumuşak bir bağlantı oluşturur, böylece garip dosya adlarına asılmayacaktır. Bu önemlidir çünkü cat'argümanları üzerinden bir boru üzerinden beslenir xargs. catbireyin çıkış kaydedilir <&3ise sed pbu dosyada hatları vardır kadar çok kez rints ilk parametre her çizgi - ve komut dosyası da bir boru aracılığı ile beslenmektedir. Yine pastegirişini birleştirir, ancak bu kez -standart girişi ve bağlantı adı için sadece iki argüman alır /dev/fd/3.

Bu son - /dev/fd/[num]bağlantı - herhangi bir linux sistemi ve daha birçok şey üzerinde çalışmalıdır, ancak bununla birlikte adlandırılmış bir boru oluşturmazsa mkfifove bunun yerine de kullanılmalıdır.

Yaptığı son şey, rmçıkmadan önce oluşturduğu yumuşak bağlantıdır.

Bu sürüm aslında hala sistemimde daha hızlı . Sanırım daha fazla uygulama yürütmesine rağmen, argümanlarını derhal vermeye başlar - oysa önce hepsini istiflemeden önce.

time pairs2 /tmp/tmp | wc -l

1000000
pairs2 /tmp/tmp  0.30s user 0.09s system 178% cpu 0.218 total
wc -l  0.03s user 0.02s system 26% cpu 0.218 total

Çiftler fonksiyonunun bir dosyada olduğunu varsayalım, eğer değilse nasıl beyan edersiniz?

@Jidder - neyi nasıl beyan ederim? Sadece bir terminale kopyalayıp yapıştırabilirsiniz, değil mi?
mikeserv

1
İşlevi bildiriniz. Eğer sen-si olmak kaçış newlines olacağını düşündüm, ben sadece kod yapıştırarak dikkatli, teşekkürler :) Ayrıca bu son derece hızlı, güzel cevap!

@Jidder - Bunları genellikle ctrl+v; ctrl+jbenim gibi yeni satırlar almak için canlı bir kabukta yazıyorum.
mikeserv

@Jidder - çok teşekkür ederim. Ve dikkatli olmak akıllıca olacaktır - sizin için iyi. Bir dosyada da çalışırlar - onu ve . ./file; fn_namebu durumda kopyalayabilirsiniz .
mikeserv

5

Bunu her zaman kabuğunuzda yapabilirsiniz:

while read i; do 
    while read k; do echo "$i $k"; done < sample.txt 
done < sample.txt 

awkÇözümünüzden çok daha yavaştır (makinemde, 1000 satır için ~ 11 saniye, ~ 0.3 saniye awk), ancak en azından asla bellekte birkaç satırdan fazlasını tutmaz.

Yukarıdaki döngü, örneğinizde bulunan çok basit veriler için geçerlidir. Ters eğik çizgileri boğacak ve sondaki ve önde gelen alanları yiyecektir. Aynı şeyin daha sağlam bir versiyonu:

while IFS= read -r i; do 
    while IFS= read -r k; do printf "%s %s\n" "$i" "$k"; done < sample.txt 
done < sample.txt 

perlBunun yerine başka bir seçenek kullanmaktır :

perl -lne '$line1=$_; open(A,"sample.txt"); 
           while($line2=<A>){printf "$line1 $line2"} close(A)' sample.txt

Yukarıdaki komut dosyası, giriş dosyasının ( -ln) her satırını okur $l, sample.txttekrar kaydeder ve her satırı birlikte basar $l. Sonuç tüm çift kombinasyonlar olurken, sadece 2 satır hafızaya kaydedilir. Sistemimde, bu 0.61000 satırda sadece birkaç saniye sürdü .


Vay canına, teşekkürler! Perl çözümünün neden bash'den çok daha hızlı olduğunu merak ediyorum
Tom Hayden

@TomHayden temel olarak perl, awk gibi bash'den çok daha hızlıdır.
terdon

1
While döngüsü için aşağı çekmek zorunda kaldı. Orada 4 farklı kötü uygulama var. Sen daha iyi bilirsin.
Stéphane Chazelas

1
@ StéphaneChazelas iyi, buradaki cevabınıza dayanarak, sorunun olabileceği herhangi bir durum düşünemedim echo. Yazdıklarım ( printfşimdi ekledim ) hepsiyle çalışmalı mı? whileDöngü gelince , neden? Sorun nedir while read f; do ..; done < file? Elbette bir fordöngü önermiyorsunuz! Diğer alternatif nedir?
terdon

2
@cuonglm, biri sadece bundan kaçınmak için olası bir nedenden bahsetmektedir. Out of kavramsal , güvenilirlik , okunabilirlik , performans ve güvenlik yönlerinden, sadece kapakları güvenilirliği .
Stéphane Chazelas

4

İle zsh:

a=(
Row1,10
Row2,20
Row3,30
Row4,40
)
printf '%s\n' $^a' '$^a

$^abir dizi, dizi için küme ayracı benzeri genişletmeyi (gibi {elt1,elt2}) açar.


4

Oldukça hızlı sonuçlar için bu kodunu derleyebilirsiniz .
1000 satırlık bir dosyada yaklaşık 0,19 - 0,27 saniye içinde tamamlanır.

Şu anda 10000satır 1000başına karakterleri olsaydı 10mbben bir sorun olacağını düşünmek olmaz bellek daha az kullanır bellek (ekrana baskı hızlandırmak için) satırları okur . Yine de bu bölümü tamamen kaldırabilir ve bir soruna neden olursa doğrudan ekrana yazdırabilirsiniz.

Dosyayı kaydetmek için dosyanın adı ve bu kodun kaydedildiği dosya g++ -o "NAME" "NAME.cpp"
nerede? Kullanarak derleyebilirsiniz .NAMENAME.cpp

CTEST.cpp:

#include <iostream>
#include <string>
#include <fstream>
#include <iomanip>
#include <cstdlib>
#include <sstream>
int main(int argc,char *argv[])
{

        if(argc != 2)
        {
                printf("You must provide at least one argument\n"); // Make                                                                                                                      sure only one arg
                exit(0);
   }
std::ifstream file(argv[1]),file2(argv[1]);
std::string line,line2;
std::stringstream ss;
int x=0;

while (file.good()){
    file2.clear();
    file2.seekg (0, file2.beg);
    getline(file, line);
    if(file.good()){
        while ( file2.good() ){
            getline(file2, line2);
            if(file2.good())
            ss << line <<" "<<line2 << "\n";
            x++;
            if(x==10000){
                    std::cout << ss.rdbuf();
                    ss.clear();
                    ss.str(std::string());
            }
    }
    }
}
std::cout << ss.rdbuf();
ss.clear();
ss.str(std::string());
}

gösteri

$ g++ -o "Stream.exe" "CTEST.cpp"
$ seq 10 10 10000 | nl -s, > testfile
$ time ./Stream.exe testfile | wc -l
1000000

real    0m0.243s
user    0m0.210s
sys     0m0.033s

3
join -j 2 file.txt file.txt | cut -c 2-
  • mevcut olmayan bir alana katılın ve ilk alanı kaldırın

Alan 2, file.txt dosyasındaki tüm öğeler için boş ve eşittir, bu nedenle joinher bir öğeyi diğerleriyle birleştirir: aslında Kartezyen ürünü hesaplamaktır.


2

Python ile bir seçenek dosyayı bellek eşlemektir ve Python düzenli ifade kütüphanesinin doğrudan bellek eşlemeli dosyalarla çalışabilmesinden faydalanır. Her ne kadar bu dosya üzerinde iç içe döngüler çalıştırıyor gibi görünse de, bellek eşleme, işletim sisteminin kullanılabilir fiziksel RAM'i en iyi şekilde oynatmasını sağlar

import mmap
import re
with open('test.file', 'rt') as f1, open('test.file') as f2:
    with mmap.mmap(f1.fileno(), 0, flags=mmap.MAP_SHARED, access=mmap.ACCESS_READ) as m1,\
        mmap.mmap(f2.fileno(), 0, flags=mmap.MAP_SHARED, access=mmap.ACCESS_READ) as m2:
        for line1 in re.finditer(b'.*?\n', m1):
            for line2 in re.finditer(b'.*?\n', m2):
                print('{} {}'.format(line1.group().decode().rstrip(),
                    line2.group().decode().rstrip()))
            m2.seek(0)

Bellek verimliliği hala endişe verici olsa da, alternatif olarak Python'da hızlı bir çözüm

from itertools import product
with open('test.file') as f:
    for a, b  in product(f, repeat=2):
        print('{} {}'.format(a.rstrip(), b.rstrip()))
Row1,10 Row1,10
Row1,10 Row2,20
Row1,10 Row3,30
Row1,10 Row4,40
Row2,20 Row1,10
Row2,20 Row2,20
Row2,20 Row3,30
Row2,20 Row4,40
Row3,30 Row1,10
Row3,30 Row2,20
Row3,30 Row3,30
Row3,30 Row4,40
Row4,40 Row1,10
Row4,40 Row2,20
Row4,40 Row3,30
Row4,40 Row4,40

Tanımı gereği tüm dosyayı hafızada tutmayacak mı? Python'u bilmiyorum ama diliniz kesinlikle bunu yapacağını gösteriyor.
terdon

1
@terdon, bellek eşleme çözümüne atıfta bulunuyorsanız, işletim sistemi, kullanılabilir fiziksel RAM'e dayanarak karşılayabileceği kadar çok dosyayı bellekte tutar. Kullanılabilir fiziksel RAM'in dosya boyutunu aşması gerekmez (ekstra fiziksel RAM'e sahip olmanın avantajlı bir durum olmasına rağmen). En kötü durumda, bu diskteki dosya veya daha da kötüsü arasında döngü hızına düşebilir. Bu yaklaşımın temel avantajı, mevcut fiziksel RAM'in şeffaf kullanımıdır, çünkü bu zamanla dalgalanabilecek bir şeydir
iruvar

1

Bash'da ksh, yalnızca kabuk yerleşiklerini kullanarak da çalışmalıdır:

#!/bin/bash
# we require array support
d=( $(< sample.txt) )
# quote arguments and
# build up brace expansion string
d=$(printf -- '%q,' "${d[@]}")
d=$(printf -- '%s' "{${d%,}}' '{${d%,}}")
eval printf -- '%s\\n' "$d"

Bu, tüm dosyayı bir kabuk değişkeninde bellekte tutarken, dosyaya yalnızca tek bir okuma erişimi gerektirdiğini unutmayın.


1
Ben OP için bütün mesele dosyayı bellekte tutmak değil olduğunu düşünüyorum. Aksi takdirde, mevcut gawk yaklaşımları hem daha basit hem de çok daha hızlıdır. Bunun birkaç gigabayt boyutundaki metin dosyalarıyla çalışması gerektiğini tahmin ediyorum.
terdon

Evet, bu kesinlikle doğru - Bunu yapmam gereken ve bellekte tutmak istemediğim birkaç BÜYÜK veri
Tom Hayden

Bellek ile kısıtlı olmanız durumunda, @terdon
Franki

0

sed çözüm.

line_num=$(wc -l < input.txt)
sed 'r input.txt' input.txt | sed -re "1~$((line_num + 1)){h;d}" -e 'G;s/(.*)\n(.*)/\2 \1/'

Açıklama:

  • sed 'r file2' file1 - dosya1'in her satırı için dosya2'nin tüm dosya içeriğini okuyun.
  • İnşaat 1~i, 1. çizgi, sonra 1 + i çizgi, 1 + 2 * i, 1 + 3 * i, vb. Anlamına 1~$((line_num + 1)){h;d}gelir . Bu nedenle, harabelleğe eski sivri çizgi, delete desen boşluğu ve yeni döngüyü başlatır.
  • 'G;s/(.*)\n(.*)/\2 \1/'- önceki adımda seçilenler hariç, tüm satırlar için aşağıdakileri yapın: Gtutma tamponundaki satır ve geçerli satıra ekleyin. Ardından satırların yerlerini değiştirin. Oldu current_line\nbuffer_line\n, oldubuffer_line\ncurrent_line\n

Çıktı

Row1,10 Row1,10
Row1,10 Row2,20
Row1,10 Row3,30
Row1,10 Row4,40
Row2,20 Row1,10
Row2,20 Row2,20
Row2,20 Row3,30
Row2,20 Row4,40
Row3,30 Row1,10
Row3,30 Row2,20
Row3,30 Row3,30
Row3,30 Row4,40
Row4,40 Row1,10
Row4,40 Row2,20
Row4,40 Row3,30
Row4,40 Row4,40
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.