Jak losowo zamieniać tekst z pliku?


9

Jak mogę losowo zastąpić określone ciągi w jednym pliku tekstowym ciągami z innego pliku? Na przykład:

file1.txt(file has more than 200 lines):
moonwalker@address.com
hansolo@address.com
anakinskywalker@address.com
obiwankenobi@address.com
darthvader@address.com

file2.txt(file has 10-20 lines):
@adress1.com
@adress2.com
@adress3.com
@adress4.com
@adress5.com

output.txt:
moonwalker@address4.com
hansolo@address1.com
anakinskywalker@address5.com
obiwankenobi@address2.com
darthvader@address3.com

4
To nie jest przypadkowe, wygląda na to, że nie chcesz powtarzać niczego. Czy chcesz, aby był tak naprawdę losowy, czy może każdy wiersz drugiego pliku tekstowego może być użyty tylko raz? Ponadto, czy to musi być bash, czy jesteś otwarty na inne narzędzia?
terdon

1
@terdon Wygląda na to, że chce losowej permutacji (wszystkie 5 elementów, ale w losowej kolejności). Losowa permutacja jest w rzeczywistości losowa, wystarczy wyeliminować już wybrane elementy podczas losowego wybierania następnego elementu. Czasami nazywany „sortowaniem losowym”
thomasrutter,

1
@ thomasrutter tak, wiem o tym i tak właśnie robi moja odpowiedź. Ale dlatego poprosiłem OP o wyjaśnienie, ponieważ zarówno losowa permutacja, jak i losowa selekcja byłyby rozsądne w zależności od tego, czego potrzebują.
terdon

Odpowiedzi:


9

Jeśli naprawdę chcesz losowego wyboru, oto jeden ze sposobów awk:

awk '
  BEGIN{FS="@"; OFS=""} 
  NR==FNR{a[NR]=$0; n++; next} 
  {$2=a[int(1 + n * rand())]; print}
' file2.txt file1.txt
moonwalker@adress2.com
hansolo@adress2.com
anakinskywalker@adress5.com
obiwankenobi@adress1.com
darthvader@adress3.com

OTOH, jeśli chcesz losową permutację adresów, sugerowałbym coś takiego

paste -d '' <(cut -d'@' -f1 file1.txt) <(sort -R file2.txt)
moonwalker@adress2.com
hansolo@adress1.com
anakinskywalker@adress5.com
obiwankenobi@adress4.com
darthvader@adress3.com

1
Miły! Chciałem to zrobić, pasteale nie przyszło mi do głowy, cutaby usunąć niepasujące pole.
terdon

2
Jednym minusem rozwiązania dotyczącego wklejania jest to, że plik1 ma więcej linii niż plik2. Zamiast tego <(sort -R file2.txt)możemy użyć czegoś takiego <(yes "$(<file2.txt)" | head -n $(wc -l < file1.txt) | sort -R)- może to wypaczyć losowość na korzyść linii bliżej góry pliku2.
glenn jackman

10

Możesz zaimplementować ten algorytm:

  • Załaduj zawartość file2.txtdo tablicy
  • Dla każdej linii w file1.txt:
    • Wyodrębnij część nazwy
    • Uzyskaj losowy adres
    • Wydrukuj wydruk poprawnie sformatowany

Lubię to:

mapfile -t addresses < file2.txt
while IFS='' read -r orig || [[ -n "$orig" ]]; do
    ((index = RANDOM % ${#addresses[@]}))
    name=${orig%%@*}
    echo "$name${addresses[index]}"
done < file1.txt

(Specjalne podziękowania dla @GlennJackman i @dessert za ulepszenia.)


3
Zastanów się nad zapełnieniem tablicy mapfile -t addresses < file2.txt- używanie cattakich tematów dzieli cię na słowa i rozwija nazwy plików.
glenn jackman

2
Czy to przechwytuje ostatnią niepustą linię, file1.txtjeśli ten plik nie kończy się pustą linią (przepraszam, w tej chwili nie można przetestować)? Jeśli nie, zalecam while IFS='' read -r orig || [[ -n "$orig" ]]; do, zobacz Odczytywanie pliku wiersz po wierszu, przypisując wartość do zmiennej · SO .
deser

2
@janos Właśnie znalazłem bardzo dobre pytanie na ten temat: Skrypt powłoki przeczytał brakujący ostatni wiersz
deser

5

Możesz użyć shuf(może być to konieczne sudo apt install shuf), aby przetasować linie drugiego pliku, a następnie użyć ich do zamiany:

$ awk -F'@' 'NR==FNR{a[NR]=$1;next}{print a[FNR]"@"$2} ' file1 <(shuf file2)
moonwalker@adress3.com
hansolo@adress1.com
anakinskywalker@adress5.com
obiwankenobi@adress4.com
darthvader@adress2.com

shufpo prostu losuje kolejność linii wejściowych. awkKomenda będzie najpierw przeczytać cały plik1 ( NR==FNRbędzie tylko prawda, podczas gdy pierwszy plik jest odczytywany) i zapisuje drugie pole (pola są zdefiniowane @, więc jest to domena) w tablicy asocjacyjnej a, których wartości są domeny i których klucze to numery linii. Następnie, gdy przejdziemy do następnego pliku, po prostu wydrukuje to, co było zapisane adla tego numeru linii, wraz z tym, co znajduje się w pliku 2 dla tego samego numeru linii.

Zauważ, że zakłada to, że oba pliki mają dokładnie taką samą liczbę linii i tak naprawdę nie są „losowe”, ponieważ nie pozwalają na powtórzenie niczego. Ale wygląda na to, o co chciałeś poprosić.


5

Rozwiązanie Python 2.7 i 3

To rozwiązanie zastępuje pierwsze wystąpienie pojedynczego podanego ciągu („igły”) w każdym wierszu pliku wejściowego ciągiem wybranym za każdym razem losowo z zestawu wierszy listy ciągów zamienników.

#!/usr/bin/python
from __future__ import print_function
import sys, random

needle = sys.argv[1]

if sys.argv[2] == '-':
    f_replacements = sys.stdin
else:
    f_replacements = open(sys.argv[2])
with f_replacements:
    replacements = [l.rstrip('\n') for l in f_replacements]
if not replacements:
    raise ValueError('No replacement strings given')

if len(sys.argv) <= 3 or sys.argv[3] == '-':
    f_in = sys.stdin
else:
    f_in = open(sys.argv[3])
with f_in:
    for s in f_in:
        rep = replacements[random.randrange(len(replacements))]
        print(s.rstrip('\n').replace(needle, rep, 1))

Zakotwiczenie igły na początku lub na końcu łańcucha lub użycie wyrażeń regularnych powinno być prawie trywialne.

Stosowanie

python replace-random.py NEEDLE REPLACEMENTS-FILE [INPUT-FILE]

Przykład:

python replace-random.py '@address.com' file2.txt file1.txt

lub

python replace-random.py '@address.com' file2.txt < file1.txt

3

Oto perlowy sposób:

#!/usr/bin/perl
use warnings;
use strict;
use Tie::File;

tie my @file1,'Tie::File','file1.txt' or die "Can't open file1.txt\n";
tie my @file2,'Tie::File','file2.txt' or die "Can't open file2.txt\n";

for my $file_index (0..$#file1) {
   my $suffix = $file2[int(rand($#file2+1))];
   $file1[$file_index] =~ s/@.*$/$suffix/;
}

untie @file1;
untie @file2;

2

Kolejne rozwiązanie bash. Używa wbudowanej funkcji zamiany napisów bash. Zakłada również, że file2.txtzawiera tylko ciągi zastępujące. Jeśli nie, można je najpierw przefiltrować za pomocągrep -o <replace> file2.txt

Z shuf

#search string
Search="@address.com"
for lines in $(grep $Search file1.txt)
do 
    echo ${lines/$Search/$(shuf file2.txt -n 1)} 
done

Bez shuf(prawie czysty bash)

Tutaj musimy najpierw stworzyć funkcję, która naśladuje shuftak

bshuf () 
{ 
    nlines=$(( $(wc -l < $1) + 1))
    rand=0
    while [ "$rand" -eq 0 ]; do
        rand=$(( $RANDOM % nlines ))
    done
    echo $(head -n $rand $1 | tail -1)
}

To jest podobne

for lines in $(grep $Search file1.txt) 
do 
    echo ${lines/$Search/$(bshuf file2.txt)}
done

Test:

$ for lines in $(grep $Search file1.txt); do echo ${lines/$Search/$(bshuf file2.txt)} ; done
moonwalker@adress4.com
hansolo@adress2.com
anakinskywalker@adress2.com
obiwankenobi@adress3.com
darthvader@adress5.com
$ 
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.