Bash: переменная область ассоциативного массива в цикле [dубликат]

Зависит от дизайна вашей оболочки.

Соответствует ли это стандарту POSIX?

Если да, то ваша оболочка должна прочитать переменную среды ENV в начале любого интерактивного сеанса и прочитать / выполнить содержимое файла (ов), к которому относится переменная ENV. Никакой другой файл не должен читаться в соответствии со стандартом. Это минимальный минимум для POSIX-конформации в отношении чтения файлов при запуске.

Соответствует ли это стандарту POSIX?

Как насчет настройки?

124
задан 14 May 2017 в 11:22

4 ответа

Хммм ... Я бы почти поклялся, что это сработало для оригинальной оболочки Bourne, но сейчас у меня нет доступа к текущей копии.

Однако есть очень тривиальный обходной путь к проблеме.

Измените первую строку скрипта:

#!/bin/bash

на

#!/bin/ksh

Et voila! Чтение в конце конвейера работает очень хорошо, если у вас установлена ​​оболочка Korn.

2
ответ дан 15 August 2018 в 16:56

Это интересный вопрос и коснуться очень простой концепции в оболочке Борна и подоболочке. Здесь я предоставляю решение, отличное от предыдущих решений, делая какую-то фильтрацию. Я приведу пример, который может быть полезен в реальной жизни. Это фрагмент для проверки загруженных файлов, соответствующих знанию контрольных сумм. Файл контрольной суммы выглядит следующим образом (показано всего 3 строки):

49174 36326 dna_align_feature.txt.gz
54757     1 dna.txt.gz
55409  9971 exon_transcript.txt.gz

Сценарий оболочки:

#!/bin/sh

.....

failcnt=0 # this variable is only valid in the parent shell
#variable xx captures all the outputs from the while loop
xx=$(cat ${checkfile} | while read -r line; do
    num1=$(echo $line | awk '{print $1}')
    num2=$(echo $line | awk '{print $2}')
    fname=$(echo $line | awk '{print $3}')
    if [ -f "$fname" ]; then
        res=$(sum $fname)
        filegood=$(sum $fname | awk -v na=$num1 -v nb=$num2 -v fn=$fname '{ if (na == $1 && nb == $2) { print "TRUE"; } else { print "FALSE"; }}')
        if [ "$filegood" = "FALSE" ]; then
            failcnt=$(expr $failcnt + 1) # only in subshell
            echo "$fname BAD $failcnt"
        fi
    fi
done | tail -1) # I am only interested in the final result
# you can capture a whole bunch of texts and do further filtering
failcnt=${xx#* BAD } # I am only interested in the number
# this variable is in the parent shell
echo failcnt $failcnt
if [ $failcnt -gt 0 ]; then
    echo $failcnt files failed
else
    echo download successful
fi

Родитель и подоболочка взаимодействуют через команду echo. Вы можете легко выбрать текст для родительской оболочки. Этот метод не нарушает ваш нормальный образ мышления, просто нужно выполнить некоторую пост-обработку. Для этого вы можете использовать grep, sed, awk и т. Д.

0
ответ дан 15 August 2018 в 16:56

Как насчет очень простого метода

    +call your while loop in a function 
     - set your value inside (nonsense, but shows the example)
     - return your value inside 
    +capture your value outside
    +set outside
    +display outside


    #!/bin/bash
    # set -e
    # set -u
    # No idea why you need this, not using here

    foo=0
    bar="hello"

    if [[ "$bar" == "hello" ]]
    then
        foo=1
        echo "Setting  \$foo to $foo"
    fi

    echo "Variable \$foo after if statement: $foo"

    lines="first line\nsecond line\nthird line"

    function my_while_loop
    {

    echo -e $lines | while read line
    do
        if [[ "$line" == "second line" ]]
        then
        foo=2; return 2;
        echo "Variable \$foo updated to $foo inside if inside while loop"
        fi

        echo -e $lines | while read line
do
    if [[ "$line" == "second line" ]]
    then
    foo=2;          
    echo "Variable \$foo updated to $foo inside if inside while loop"
    return 2;
    fi

    # Code below won't be executed since we returned from function in 'if' statement
    # We aready reported the $foo var beint set to 2 anyway
    echo "Value of \$foo in while loop body: $foo"

done
}

    my_while_loop; foo="$?"

    echo "Variable \$foo after while loop: $foo"


    Output:
    Setting  $foo 1
    Variable $foo after if statement: 1
    Value of $foo in while loop body: 1
    Variable $foo after while loop: 2

    bash --version

    GNU bash, version 3.2.51(1)-release (x86_64-apple-darwin13)
    Copyright (C) 2007 Free Software Foundation, Inc.
-1
ответ дан 15 August 2018 в 16:56
  • 1
    Может быть, здесь есть достойный ответ, но вы отбили форматирование до такой степени, что неприятно пытаться читать и читать. – Mark Amery 10 September 2016 в 22:42

ОБНОВЛЕНО # 2

Объяснение в ответе Голубого Луны.

Альтернативные решения:

Устранить echo

while read line; do
...
done <<EOT
first line
second line
third line
EOT

Добавьте эхо в документ здесь [-d5]

while read line; do
...
done <<EOT
$(echo -e $lines)
EOT

Запустите echo в фоновом режиме:

coproc echo -e $lines
while read -u ${COPROC[0]} line; do 
...
done

Перенаправить на дескриптор файла явно (Mind the space в < <!):

exec 3< <(echo -e  $lines)
while read -u 3 line; do
...
done

Или просто перенаправить на stdin:

while read line; do
...
done < <(echo -e  $lines)

И один для chepner (исключая echo): [ ! d9]

arr=("first line" "second line" "third line");
for((i=0;i<${#arr[*]};++i)) { line=${arr[i]}; 
...
}

Переменная $lines может быть преобразована в массив без запуска новой под-оболочки. Символы \ и n должны быть преобразованы в некоторый символ (например, настоящий новый символ строки) и использовать переменную IFS (Internal Field Separator) для разделения строки на элементы массива. Это можно сделать так:

lines="first line\nsecond line\nthird line"
echo "$lines"
OIFS="$IFS"
IFS=$'\n' arr=(${lines//\\n/$'\n'}) # Conversion
IFS="$OIFS"
echo "${arr[@]}", Length: ${#arr[*]}
set|grep ^arr

Результат

first line\nsecond line\nthird line
first line second line third line, Length: 3
arr=([0]="first line" [1]="second line" [2]="third line")
38
ответ дан 15 August 2018 в 16:56
  • 1
    +1 для this-doc, поскольку единственной целью переменной lines, похоже, является подача петли while. – chepner 31 May 2013 в 17:44
  • 2
    @chepner: Thx! Я добавил еще один, посвященный Тебе! – TrueY 31 May 2013 в 17:57
  • 3
    Существует еще одно решение, данное здесь : for line in $(echo -e $lines); do ... done – dma_k 20 January 2016 в 00:20
  • 4
    @dma_k Спасибо за ваш комментарий! Это решение приведет к 6 строкам, содержащим одно слово. Запрос OP был другим ... – TrueY 20 January 2016 в 12:59
  • 5
    upvoted. работающее эхо в подоболочке внутри - это одно из немногих решений, которые работали в золе – David 26 November 2016 в 09:03

Другие вопросы по тегам:

Похожие вопросы: