2009-02-13 3 views
10

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

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

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

a 1 
b 2 
c 3 

Я пытаюсь поддерживать современную портативную оболочку Борна, так Баш/КШ массивы не доступны. Обстрел в awk был бы приемлемым в крайнем случае, но я предпочел бы сохранить это в чистом состоянии, если это возможно.

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

ответ

4

Это немного Hacky, но делает работу:

#!/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

FWIW это не работает под/bin/sh на сервере solaris. он застревает в бесконечном цикле, повторяющем «1 а». – jj33

+0

О, хорошо, опросник принял, поэтому я думаю, что это работает на него. У меня нет сервера Solaris. Если вы добавите комментарий с исправлением (это будет что-то в sed rexgep), я добавлю его, но до тех пор я оставлю ответ в виде триггеров. –

+0

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

1

NEVERMIND, SAW "BOURNE" и думал, что "BOURNE AGAIN". Оставив это здесь, потому что это может быть полезно кому-то, но явно не ответ на заданный вопрос, извините!

-

Это имеет некоторые недостатки (не корректно обрабатывать списки, которые имеют разные размеры), но это работает на примере вы дали:

#!/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, но приведенное выше является реализацией pure-bash по запросу

Комментируя принятый ответ, это не сработало для меня ни в Linux, ни в Solaris, проблема заключалась в символьном классе \ S ярлык в regexp для 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

Принятый ответ является вторым в этом сообщении, основанным на превосходной переносимости. – emk

1

Как один лайнер:

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 
12

Вероятно, не переносимы (посмотрите на все эти 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

Это должно быть довольно чистое решение, но если вы не используете подсистему 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.$$ 
1

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

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 
0

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

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 

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

1
$ 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 
2

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

#!/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 

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

#!/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 
+0

Чтобы дать хороший ответ, лучше всего предложить какое-то объяснение решения, а не просто код публикации. – Scriptable

Смежные вопросы