Який портативний (POSIX) спосіб досягти заміни процесу?


25

Деякі оболонки, як-от bash, підтримують Process Substitution, який є способом подання результату процесу у вигляді файлу, наприклад:

$ diff <(sort file1) <(sort file2)

Однак ця конструкція не є POSIX і, отже, не портативна. Як може обробляти підміна бути досягнутий в POSIX співдружню чином (тобто той , який працює в /bin/sh) ?

Зауважте: питання не задається питанням, як відрізняти два відсортовані файли - це лише надуманий приклад демонстрації заміни процесу !


mywiki.wooledge.org/ProcessSubstitution має зауваження, що mywiki.wooledge.org/NamedPipes може бути використаний ..
Sundeep

2
Дивіться також unix.stackexchange.com/a/43536/117549
Jeff Schaller

1
POSIX не вимагає /bin/shоболонки POSIX, лише shдесь є команда, яка називається десь, яка в правильному середовищі сумісна з POSIX. Наприклад, на Solaris 10 /bin/shце не оболонка POSIX, це стародавня оболонка Борна і оболонка POSIX /usr/xpg4/bin/sh.
Стефан Шазелас

Відповіді:


17

Ця функція була введена ksh(вперше задокументована в ksh86) і використовує цю /dev/fd/nфункцію (додається незалежно в деяких системах BSD та AT&T раніше). До kshі до ksh93u, воно не працюватиме, якщо ваша система не мала підтримку / dev / fd / n. zsh, bash і ksh93u+вище можуть використовувати тимчасові названі труби (названі труби додані в SysIII, я вважаю), де / dev / fd / n недоступні.

У системах, де доступні (POSIX не вказує їх), ви можете зробити заміну самостійно ( ):/dev/fd/ndiff <(cmd1) <(cmd2)

{
  cmd1 4<&- | {
    # in here fd 3 points to the reading end of the pipe
    # from cmd1, while fd 0 has been restored from the original
    # stdin (saved on fd 4, now closed as no longer needed)

    cmd2 3<&- | diff /dev/fd/3 -

  } 3<&0 <&4 4<&- # restore the original stdin for cmd2

} 4<&0 # save a copy of stdin for cmd2

Однак це не працює з ksh93Linux, як там, оболонки труби реалізуються разом з socketpairs, а не відкриттями, /dev/fd/3де fd 3 бали на сокет не працює в Linux.

Хоча POSIX не вказує . У ньому вказуються названі труби. Названі труби працюють як звичайні труби, за винятком того, що ви можете отримати доступ до них з файлової системи. Проблема тут полягає в тому, що вам потрібно створити тимчасові та очистити їх згодом, що важко зробити надійно, особливо враховуючи, що POSIX не має стандартного механізму (як, наприклад , у деяких системах) для створення тимчасових файлів чи каталогів, а також обробку сигналів портативно (прибирати під час відключення чи вбивства) також важко зробити портативно./dev/fd/nmktemp -d

Ви можете зробити щось на кшталт:

tmpfifo() (
  n=0
  until
    fifo=$1.$$.$n
    mkfifo -m 600 -- "$fifo" 2> /dev/null
  do
    n=$((n + 1))
    # give up after 20 attempts as it could be a permanent condition
    # that prevents us from creating fifos. You'd need to raise that
    # limit if you intend to create (and use at the same time)
    # more than 20 fifos in your script
    [ "$n" -lt 20 ] || exit 1
  done
  printf '%s\n' "$fifo"
)

cleanup() { rm -f -- "$fifo"; }
fifo=$(tmpfifo /tmp/fifo) || exit

cmd2 > "$fifo" & cmd1 | diff - "$fifo"
rm -f -- "$fifo"

(не піклуючись про обробку сигналу тут).


Наведений приклад труби для мене цілком зрозумілий (я думаю, 10 - це довільна межа?), Але я не можу зрозуміти ваш /dev/fd/nприклад. Чому дескриптор 4 закривається двічі? (І так є дескриптор 3.) Я загублений.
Wildcard

@Wildcard, він закритий для команд, які йому не потрібні. Тут потрібні лише розрізнення, які потрібні fd 3. Нікому не потрібен fd 4, він використовується лише для розповсюдження початкового stdin до cmd2( dup2(0,4)зовнішньому {...}, відновленому dup2(4,0)зсередини {...}).
Стефан Шазелас

Ви можете використати, mktemp -dщоб переконатися, що ви можете отримати FIFO, оскільки ніхто не повинен писати у ваш абсолютно новий випадковий тимчасовий каталог.
Даніель Н

@DanielH, я вже згадую mktemp -d. Але це не стандартна команда / POSIX.
Стефан Шазелас

Так, я цього не усвідомлював. На жаль Швидкий пошук, схоже, показує, що більшість систем підтримує його, тому він все ще може бути портативним, але це не POSIX.
Даніель Н

-1

Якщо потрібно, щоб уникнути втраченої змінної у корисному cmd | while read A B C, а не:

VAR="before"
while read A B C 
do
  VAR="$A $VAR"
done < <(cmd)
echo "$VAR"

Ви можете використовувати:

VAR="before"
while read A B C 
do
  VAR="$A $VAR"
done << EndOfText
`cmd`
EndOfText
echo "$VAR"

Отже, щоб відповісти на питання:

sort file1 | diff /dev/stdin /dev/stdout 2<<EOT
`sort file2`
EOT
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.