2015-02-25 27 views
7

Mam 10 plików tekstowych i chcę paste każdy plik z jego parą, tak, że mam 5 wszystkich plików.Pętle nad parami wartości w bashu

Próbowałem następujące:

for i in 4_1 5_1 6_1 7_1 8_1 
do 
for j in 4_2 5_2 6_2 7_2 8_2 
do 
paste ${i}.txt ${j}.txt > ${i}.${j}.txt 
done 
done 

Jednak ten kod łączy każdą możliwą kombinację zamiast po prostu łącząc pasujące pary.

Więc chciałbym plik 4_1.txt być sparowany z 4_2.txt, 5_1.txt z 5_2.txt itp

+0

Konieczne jest zapętlenie przedrostków i przyrostków, nie pełnych nazw plików. –

+0

Zachowanie, które otrzymujesz, jest takie samo, jak to, co zrobiłaby zagnieżdżona pętla 'for' w * dowolnym * języku; nie ma w tym nic szczególnego. –

+0

Za każdym razem, gdy chcę zrobić coś stosunkowo prostego, jak na przykład powtarzanie par wartości, Bash sprawia, że ​​obciążenie wygląda jak nie do pokonania. Dlatego nie używam bashu, gdy potrzebuję czegoś bardziej skomplikowanego niż lista wywołań powłoki. – ThorSummoner

Odpowiedz

6

Jeśli chcesz użyć jednej zmiennej i wykonania i działania z nim, wystarczy użyć jednej pętli:

for file in 4 5 6 7 8 
do 
    paste "${file}_1" "${file}_2" 
done 

to zrobi

paste 4_1 4_2 
paste 5_1 5_2 
... 
+1

Lub nawet "wklej" $ {plik} "_ {1,2}' – rici

+4

Posłuchaj, Ma! Brak pętli: 'printf '% s% s \ n' {4..8} _ {1,2} | xargs -I cmd paste cmd' –

+1

Ale uruchamianie 'xargs' na niecytowanych wartościach szybko się psuje, jeśli masz nietrywialne nazwy plików, które wymagają cytowania lub ucieczki. – tripleee

13

zgadzam się z odpowiedzią obecnie proponowanej przez fedorqui w kontekst pytania, które jest obecnie zadawane. Poniższy tekst podaje jedynie bardziej ogólne odpowiedzi.

Jednym z bardziej ogólne podejście (dla bash 4.0 lub nowszej) jest do przechowywania par w tablicy asocjacyjnej:

declare -A pairs=([4_1]=4_2 [5_1]=5_2 [6_1]=6_2 [7_1]=7_2 [8_1]=8_2) 
for i in "${!pairs[@]}"; do 
    j=${pairs[$i]} 
    paste "$i.txt" "$j.txt" >"${i}.${j}.txt" 
done 

Innym (kompatybilne ze starszymi wersjami bash) jest użycie więcej niż jednego konwencjonalnego tablicę:

is=(4_1 5_1 6_1 7_1 8_1) 
js=(4_2 5_2 6_2 7_2 8_2) 
for idx in "${!is[@]}"; do 
    i=${is[$idx]} 
    j=${js[$idx]} 
    paste "$i.txt" "$j.txt" >"$i.$j.txt" 
done 
1

Istnieje wspólny wzorzec, w którym są pary plików, z których jedna nazwa pary może być łatwo wyprowadzona z drugiej. Jeśli plik, którego znasz, nazywa się X, a drugi plik to Y, masz następujące typowe przypadki użycia.

  • Do zmiany nazwy Y jest X z usuniętym rozszerzeniem i/lub datownikiem dodanym.
  • Do transkodowania, Y jest X z innym rozszerzeniem i prawdopodobnie innym katalogiem.
  • Dla wielu zadań analizy danych, X i Y dzielą niektóre części nazwy pliku, ale mają inne parametry lub rozszerzenia.

Wszystkie one nadają się do tego samego szkieletu kodu.

for x in path/to/base*.ext; do 
    dir=${x%/*} # Trim trailing file name, keep dir 
    base=${x##*/} # Trim any leading directory 

    # In this case, $y has a different subdirectory and a different extension 
    y=${dir%/to}/from/${base%.ext}.newext 

    # Maybe check if y exists? Or doesn't exist? 
    if [ -e "$y" ]; then 
     echo "$0: $y already exists -- skipping" >&2 
     continue 
    fi 

    mv or ffmpeg or awk or whatever "$x" and "$y" 
done 

Kluczem jest tu obserwacja, że ​​y mogą pochodzić z x z kilku prostych podstawień zmiennych. Pętlisz wartości x i obliczasz odpowiadającą im wartość y w pętli.

Tutaj użyliśmy powłoka wbudowanej ${variable#prefix} i ${variable%suffix} operatorów, aby powrócić wartość zmiennej z dowolnego wiodącym prefix lub końcowe suffix odpowiednio przycięte off. (Istnieje również ## i %%, aby dopasować najdłuższy, zamiast najkrótszego możliwego dopasowania. Wyrażenie po # lub % jest zwykłym wzorcem globu powłoki.) Zwykle są to wszystko, czego potrzebujesz, chociaż często widzisz skrypty sed lub awk nawet dla tego trywialnego zadania (gdzie naprawdę powinieneś zazwyczaj unikać zewnętrznego procesu), a także oczywiście dla bardziej wymagających transformacji.

Jeśli trzeba pętli ponad x plików rozrzuconych po różnych katalogach, może pętla powinna rozpocząć się z czymś jak

find dir1 dir2 etc/and/so/forth -type f -name 'x-files*.ext' -print | 
while IFS='' read -r x; do 
    : 

powszechnie postrzegane problemu w podobnych pytań jest odpowiedź niespełniające zacytować $x i $y poprawnie. Zasadniczo każda zmienna zawierająca nazwę pliku powinna być zawsze podwójnie cytowana.

gdzie X i Y są niezależne, częstym rozwiązaniem jest pętla nad tutaj dokumentu zawierającego mapowanie:

while read -r x y; do 
    : stuff with "$x" and "$y" 
done <<'____HERE' 
    first_x_value first_y_value 
    another_x  corresponding_y 
    random   surprise 
____HERE 
+0

Nie mogę napisać "X Files" z zupełnie prostą twarzą. Przepraszam. – tripleee

1

Można używać słownika/HashMap:

animals=(dog cat mouse) 
declare -A sound=(
    [dog]=barks 
    [cat]=purrs 
    [mouse]=cheeps 
) 
declare -A size=(
    [dog]=big 
    [cat]=medium 
    [mouse]=small 
) 
for animal in "${animals[@]}"; do 
    echo "$animal ${sound[$animal]} and it is ${size[$animal]}" 
done 

ten sposób, możesz pracować z krotkami (parami), "potrójnymi" itd. - dowolnym rodzajem słownika/danych hashMap.

Kredyty: Główny pomysł pochodzi z Wielkiej @ CharlesDuffy-s odpowiedzi.