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

shell posix

606 просмотра

3 ответа

3519 Репутация автора

Следующий код описывает мою проблему.

# File: foo.sh

# We have a work function that may define variables and produce some
# complex output.
work()
{
    a=foo
    ps -ef
}

# This function uses the work function.
f()
{
    # We want to filter the complex output produced by work function but
    # at the same time we want that any variables defined by work
    # function is available in this function.
    work | head -n 2

    # Since pipeline launches subshells, the variable defined by work
    # function is no longer available in this function.
    echo a: $a
}

f

Вот выход.

$ sh foo.sh
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 Jul15 ?        00:00:03 /sbin/init
a:

Так как канал создает подоболочку, что означает, что переменные, определенные в work(), теряются, когда мы возвращаемся f(), я исправил проблему таким образом.

# File: bar.sh 

# We have a work function that may define variables and produce some
# complex output.
work()
{
    a=foo
    ps -ef
}

# This function uses the work function.
f()
{
    # We want to filter the complex output produced by work function but
    # at the same time we want that any variables defined by work
    # function is available in this function.
    work > out.txt
    head -n 2 out.txt
    echo a: $a
}

f

Вот выход.

$ sh bar.sh 
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 Jul15 ?        00:00:03 /sbin/init
a: foo

Хотя второй скрипт отлично работает для этого игрушечного примера, он может привести к проблемам (хотя решаемым проблемам), когда в f()конечном итоге вызовет себя в рекурсии, что приведет к out.txtперезаписи вызываемым f (), когда вызывающий f () будет все еще полагаться на это. Конечно, это может быть решено путем достаточной заботы об использовании out.txt, то есть использования out.txtтолько до того, как f () out.txtвызовет себя, и избегайте использования ниже вызова.

Это заставило меня задуматься, есть ли способ решить эту проблему, не полагаясь на создание временного файла. Есть ли способ передать выходные данные workдругой команде, не прибегая к файловому вводу / выводу, такому, который workвыполняется в текущей оболочке? Я ищу решения, которые могут работать на любой оболочке POSIX.

Автор: Lone Learner Источник Размещён: 18.07.2016 06:51

Ответы (3)


2 плюса

5388 Репутация автора

Kludge (протестирован yash) для вывода переменной в файловый дескриптор 3 и обхода head, а затем скопировать ее обратно в STDIN :

{ { work ; echo a: $a 1>&3 ; } | head -n 2 ; } 3>&1

Выход:

UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 Jul15 ?        00:00:41 /sbin/init splash
a: foo

Обратите внимание на два уровня фигурных скобок:

  1. Непосредственно перед концом переместить файловый дескриптор 3 обратно в STDIN, не headвидя его.
  2. Держать workи echoв одной оболочке.

Тот же код, что и выше, в функции f:

f() { { { work ; echo a: $a 1>&3 ; } | head -n 2 ; } 3>&1 }
Автор: agc Размещён: 24.07.2016 02:00

1 плюс

23509 Репутация автора

Я реализовал функцию pseudopipeдля выполнения каналов запрошенным способом (т.е. первая команда канала выполняется в текущей оболочке). Обратите внимание, однако, что реализация создает временные файлы и, следовательно, не отвечает требованию избегать файлового ввода-вывода.

Использование:

Подойдет любая из следующих форм и других вариантов:

pseudopipe "cmd1|cmd2"
pseudopipe 'cmd1|cmd2'
pseudopipe cmd1\|cmd2

Ограничения текущей реализации:

  • Только трубы из двух команд обрабатываются правильно

  • Команды не должны содержать кавычек или экранированных |символов. Например, следующее не будет работать должным образом:

    pseudopipe "work|grep '|'"
    
  • Сценарий не должен использовать зарезервированные переменные PSEUDOPIPE_TMPDIR, PSEUDOPIPE_CMD1иPSEUDOPIPE_CMD2

Реализация и пример:

#!/bin/sh

pseudopipe()
{
    PSEUDOPIPE_TMPDIR=$(mktemp -d)
    PSEUDOPIPE_CMD1=$(echo "$@"|sed 's/|/\n/g'|head -n 1)
    PSEUDOPIPE_CMD2=$(echo "$@"|sed 's/|/\n/g'|tail -n 1)
    mkfifo "$PSEUDOPIPE_TMPDIR"/fifo
    eval "$PSEUDOPIPE_CMD2 < $PSEUDOPIPE_TMPDIR/fifo &" \
         "$PSEUDOPIPE_CMD1 > $PSEUDOPIPE_TMPDIR/fifo ;" \
         "rm -r $PSEUDOPIPE_TMPDIR"
    wait $!
}

work()
{
    a=foo
    ps -ef
}

f()
{
    a=bar
    echo "before pseudopipe: a: $a"
    pseudopipe "work|head -n 2"
    echo "after pseudopipe:  a: $a"
}

f

Выход:

$ sh pseudopipe.sh 
before pseudopipe: a: bar
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 Jul22 ?        00:00:06 /sbin/init splash
after pseudopipe:  a: foo
Автор: Leon Размещён: 24.07.2016 05:33

1 плюс

1380 Репутация автора

Проблема в том, что вы хотите получить несколько частей информации из подоболочки. Одно из решений состоит в том, чтобы поместить все, что вы хотите из подоболочки, в стандартный канал вывода и обработать все обратно на стороне родительской оболочки:

work()
{
    a=foo
    echo $a
    ps -ef
}

Затем у вас есть работа по синтаксическому анализу на стороне родительской оболочки. Здесь я устанавливаю разделитель на новую строку и преобразовываю возвращаемую строку в массив с одной строкой на каждый индекс массива:

f()
{
    output=$(work | head -n 3)
    IFS=$'\n'
    lines=($output)
    a=${lines[0]}
    echo ${lines[1]}
    echo ${lines[2]}
    echo a=$a
}

f

Другое решение - использовать именованные каналы и назначить каждую интересующую вас переменную в свой собственный именованный канал. На стороне subshell вы создаете и пишете каналы:

mkfifo /tmp/variable_a
echo $a > /tmp/variable_a

На стороне родительской оболочки вы читаете из одноименного канала:

read a < /tmp/variable_a
rm -f /tmp/variable_a
Автор: Daniel Wisehart Размещён: 31.07.2016 04:51
Вопросы из категории :
32x32