bash сохранить столбцы в отдельные массивы [duplicate]

Сначала вам нужно выполнить его.

Кроме того, это издевательский способ сделать это. Было бы лучше начать транзакцию, сделать SELECT, а затем определить, что делать (INSERT или UPDATE). Просто проверить, не удалось ли выполнить запрос UPDATE, это не удается, если ни одна строка не найдена.

127
задан codeforester 14 May 2017 в 08:22
поделиться

6 ответов

Вы пользователь 742342nd задаете этот FAQ bash. Ответ также описывает общий случай переменных, заданных в подоболочках, созданных трубами:

E4) Если я передаю вывод команды в read variable, то почему вывод не появляется в $variable при завершении команды чтения?

Это связано с тем, parent-child между процессами Unix. Он затрагивает все команды, выполняемые в конвейерах, а не просто вызовы read. Например, соединение вывода команды в цикле while, которое повторно вызывает read, приведет к такому же поведению.

Каждый элемент конвейера, даже встроенная или оболочечная функция, запускается в отдельном process, дочерний элемент оболочки, запускающей конвейер. Подпроцесс не может повлиять на среду своего родителя. Когда команда read устанавливает переменную на вход, эта переменная устанавливается только в подоболочке, а не в родительской оболочке. Когда подоболочка выходит, значение переменной теряется.

Многие конвейеры, которые заканчиваются на read variable, могут быть преобразованы в подстановки команд, которые будут захватывать вывод указанной команды. Затем результат может быть назначен переменной:

grep ^gnu /usr/lib/news/active | wc -l | read ngroup

можно преобразовать в

ngroup=$(grep ^gnu /usr/lib/news/active | wc -l)

Это, к сожалению, не работает, чтобы разделить текст между несколькими переменными, так как read, когда задано несколько переменных аргументов. Если вам нужно это сделать, вы можете либо использовать подстановку выше, чтобы прочитать выход в переменную, и отрубить переменную с помощью операторов расширения удаления шаблонов bash или использовать какой-то вариант следующего подхода.

Say / usr / local / bin / ipaddr - это следующий сценарий оболочки:

#! /bin/sh
host `hostname` | awk '/address/ {print $NF}'

Вместо использования

/usr/local/bin/ipaddr | read A B C D

, чтобы разбить IP-адрес локального компьютера на отдельные октеты, используйте

OIFS="$IFS"
IFS=.
set -- $(/usr/local/bin/ipaddr)
IFS="$OIFS"
A="$1" B="$2" C="$3" D="$4"

Остерегайтесь, однако, что это изменит позиционные параметры оболочки. Если вам это нужно, вы должны сохранить их, прежде чем делать это.

Это общий подход - в большинстве случаев вам не нужно устанавливать $ IFS на другое значение.

Некоторые другие пользовательские альтернативы включают в себя:

read A B C D << HERE
    $(IFS=.; echo $(/usr/local/bin/ipaddr))
HERE

и, где доступна подстановка процесса,

read A B C D < <(IFS=.; echo $(/usr/local/bin/ipaddr))
167
ответ дан Mark Amery 15 August 2018 в 14:52
поделиться
  • 1
    Вы забыли проблему Family Feud . Иногда очень сложно найти ту же комбинацию слов, что и тот, кто написал ответ, так что вы не наводнены неправильными результатами и не отфильтровываете ответ. – Evi1M4chine 29 January 2016 в 20:20
  • 2
    лучшее изменение <<< "$(echo -e "$lines")" до простого <<< "$lines" – beliy 18 May 2017 в 14:43
170
ответ дан Mark Amery 5 September 2018 в 13:59
поделиться

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

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

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

#!/bin/bash

на

#!/bin/ksh

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

2
ответ дан Jonathan Quist 15 August 2018 в 14:52
поделиться

Это интересный вопрос и коснуться очень простой концепции в оболочке Борна и подоболочке. Здесь я предоставляю решение, отличное от предыдущих решений, делая какую-то фильтрацию. Я приведу пример, который может быть полезен в реальной жизни. Это фрагмент для проверки загруженных файлов, соответствующих знанию контрольных сумм. Файл контрольной суммы выглядит следующим образом (показано всего 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
ответ дан Kemin Zhou 15 August 2018 в 14:52
поделиться

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

    +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
ответ дан Marcin 15 August 2018 в 14:52
поделиться
  • 1
    Может быть, здесь есть достойный ответ, но вы отбили форматирование до такой степени, что неприятно пытаться читать и читать. – Mark Amery 10 September 2016 в 19:42
  • 2
    Вы имеете в виду, что оригинальный код приятно читать? (Я только что последовал: p) – Marcin 11 August 2017 в 14:20

ОБНОВЛЕНО # 2

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

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

Устранить echo

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

Добавить эхо внутри документа here-is-document

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):

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
ответ дан TrueY 15 August 2018 в 14:52
поделиться
  • 1
    +1 для this-doc, поскольку единственной целью переменной lines, похоже, является подача петли while. – chepner 31 May 2013 в 13:44
  • 2
    @chepner: Thx! Я добавил еще один, посвященный Тебе! – TrueY 31 May 2013 в 13:57
  • 3
    Существует еще одно решение, данное здесь : for line in $(echo -e $lines); do ... done – dma_k 19 January 2016 в 21:20
  • 4
    @dma_k Спасибо за ваш комментарий! Это решение приведет к 6 строкам, содержащим одно слово. Запрос OP был другим ... – TrueY 20 January 2016 в 09:59
  • 5
    upvoted. работающее эхо в подоболочке внутри - это одно из немногих решений, которые работали в золе – David 26 November 2016 в 06:03