Итерация по двум спискам параллельно в / bin / sh - PullRequest
10 голосов
/ 13 февраля 2009

У меня есть два списка одинаковой длины, без пробелов в отдельных элементах:

list1="a b c"
list2="1 2 3"

Я хочу перебирать эти два списка параллельно, соединяя a с 1, b с 2 и т. Д.

a 1
b 2
c 3

Я пытаюсь поддерживать современную портативную оболочку Bourne, поэтому массивы Bash / ksh недоступны. В крайнем случае было бы приемлемо использовать awk, но я бы предпочел сохранить это в чистом виде, если это возможно.

Спасибо за любые указатели, которые вы можете предоставить!

Ответы [ 9 ]

13 голосов
/ 13 февраля 2009

Вероятно, не переносимый (посмотрите на все эти bash-isms!), Но его легко прочитать, и кто-то может найти его полезным ...

list1="a b c"
list2="1 2 3"
array1=($list1)
array2=($list2)

count=${#array1[@]}
for i in `seq 1 $count`
do
    echo ${array1[$i-1]} ${array2[$i-1]}
done
4 голосов
/ 13 февраля 2009

Это должно быть довольно чистое решение, но если вы не используете подстановку процесса bash, это требует использования временных файлов. Я не знаю, лучше это или хуже, чем вызывать cut и sed на каждой итерации.

#!/bin/sh

list1="1 2 3"
list2="a b c"
echo $list1 | sed 's/ /\n/g' > /tmp/a.$$
echo $list2 | sed 's/ /\n/g' > /tmp/b.$$

paste /tmp/a.$$ /tmp/b.$$ | while read item1 item2; do
    echo $item1 - $item2
done

rm /tmp/a.$$
rm /tmp/b.$$
4 голосов
/ 13 февраля 2009

Это немного глупо, но делает свою работу:

#!/bin/sh
list1="1 2 3"
list2="a b c"
while [ -n "$list1" ]
do
    head1=`echo "$list1" | cut -d ' ' -f 1`
    list1=`echo "$list1" | sed 's/[^ ]* *\(.*\)$/\1/'`
    head2=`echo "$list2" | cut -d ' ' -f 1`
    list2=`echo "$list2" | sed 's/[^ ]* *\(.*\)$/\1/'`
    echo $head1 $head2
done
2 голосов
/ 25 января 2015

Это должно быть переносимым, а также работает с более чем двумя списками:

#!/bin/sh
x="1 2 3 4 5"
y="a b c d e"
z="A B C D E"

while
    read current_x x <<EOF
$x
EOF

    read current_y y <<EOF
$y
EOF

    read current_z z <<EOF
$z
EOF

    [ -n "$current_x" ]
do
    echo "x=$current_x y=$current_y z=$current_z"
done

Использование позиционных параметров тоже работает. Обратите внимание, что элементы списка могут не начинаться с "-". В противном случае «установить» не удастся.

#!/bin/sh

x="1 2 3 4 5"
y="a b c d e"
z="A B C D E" 

while
    [ -n "$x" ]
do
    set $x
    current_x=$1
    shift
    x="$*"

    set $y
    current_y=$1
    shift
    y="$*"

    set $z
    current_z=$1
    shift
    z="$*"

    echo "x=$current_x y=$current_y z=$current_z"
done
1 голос
/ 26 октября 2012
$ list1="1 2 3"
$ list2="a b c"
$ echo "$list1 $list2" | awk '{n=NF/2; for (i=1;i<=n;i++) print $i,$(n+i) }'
1 a
2 b
3 c
1 голос
/ 13 февраля 2009

Решение без использования массивов:

list1="aaa1 aaa2 aaa3"
list2="bbb1 bbb2 bbb3"

tmpfile1=$( mktemp /tmp/list.XXXXXXXXXX ) || exit 1
tmpfile2=$( mktemp /tmp/list.XXXXXXXXXX ) || exit 1

echo $list1 | tr ' ' '\n'  > $tmpfile1
echo $list2 | tr ' ' '\n'  > $tmpfile2

paste  $tmpfile1  $tmpfile2

rm --force $tmpfile1  $tmpfile2
1 голос
/ 13 февраля 2009

Как один вкладыш:

list2="1 2 3"; 
list1="a b c"; 
for i in $list1; do 
    x=`expr index "$list2" " "`; 
    [ $x -eq 0 ] && j=$list2 || j=${list2:0:$x}; 
    list2=${list2:$x}; 
    echo "$i $j"; 
done
1 голос
/ 13 февраля 2009

НЕВЕРМИНД, ПИЛА "БУРН" и думала "БУРН СНОВА". Оставьте это здесь, потому что это может быть полезно для кого-то, но явно не ответ на заданный вопрос, извините!

-

У этого есть некоторые недостатки (он не изящно обрабатывает списки разных размеров), но он работает для приведенного вами примера:

#!/bin/bash

list1="a b c"
list2="1 2 3"

c=0
for i in $list1
do
  l1[$c]=$i
  c=$(($c+1))
done

c=0
for i in $list2
do
  echo ${l1[$c]} $i
  c=$(($c+1))
done

Существуют более изящные способы использования распространенных инструментов Unix, таких как awk и cut, но вышеприведенная реализация является чистой реализацией bash согласно запросу

Комментируя принятый ответ, он не работал для меня ни в Linux, ни в Solaris, проблема заключалась в ярлыке класса символов \ S в регулярном выражении для sed. Я заменил его на [^], и это сработало:

#!/bin/sh
list1="1 2 3"
list2="a b c"
while [ -n "$list1" ]
do
    head1=`echo "$list1" | cut -d ' ' -f 1`
    list1=`echo "$list1" | sed 's/[^ ]* *\(.*\)$/\1/'`
    head2=`echo "$list2" | cut -d ' ' -f 1`
    list2=`echo "$list2" | sed 's/[^ ]* *\(.*\)$/\1/'`
    echo $head1 $head2
done
0 голосов
/ 14 февраля 2009

Я работал над ответом на основе sed, когда здесь начали появляться первые решения. Но при дальнейшем исследовании выяснилось, что элементы в списке были разделены новыми строками, а не пробелами, что позволило мне выбрать решение, основанное на голове и хвосте:

original_revs="$(cd original && git rev-parse --all)" &&
working_revs="$(cd working && git rev-parse --all)" &&
while test -n "$original_revs"; do
    original_commit="$(echo "$original_revs" | head -n 1)" &&
    working_commit="$(echo "$working_revs" | head -n 1)" &&
    original_revs="$(echo "$original_revs" | tail -n +2)" &&
    working_revs="$(echo "$working_revs" | tail -n +2)" &&
    ...
done

Я публикую это на тот случай, если кто-нибудь столкнется с этим вариантом проблемы, но я присуждаю принятый ответ на основе указанной проблемы.

...