Как перенаправить стандартный ввод и вывод socat в его родительский процесс, который является сценарием bash?

То, что я хочу сделать, ДОЛЖНО быть довольно простым, но уже целый день потерян для этого. Я немного переделываю вопрос, чтобы помочь сосредоточиться:

Общая цель состоит в том, чтобы иметь «сервер» на основе bash, который, используя либо socat (предпочтительно), либо ncat (также должно работать хорошо), получает соединения от одного или нескольких «клиентов», обрабатывает данные от клиентов и, при необходимости, возвращает ответ клиентам. Этот сценарий также должен иметь возможность выполнять несколько различных задач «управления системой», которые относятся ко всему этому, и это упоминается только потому, что перенаправление сценариев туда и обратно было бы проблематичным.

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

Ранние исследования:

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

Самое близкое, что я нашел на данный момент, было в этом ответе, но он не делает то, что я хочу. Он передает оба/все операции ввода-вывода подключающемуся клиенту, тем самым делая его сервером... Что мне в нем нравится, так это то, что он действительно ДЕЙСТВИТЕЛЬНО настраивает ввод и вывод ncat полезным способом. Таким образом, идеи, содержащиеся в нем, были достаточно интересными, чтобы их можно было попробовать, и я это сделал. Однако они используют канал для ввода и перенаправление вывода на основе >, и, оглядываясь назад, возможно, мне следует придерживаться этой стратегии немного дальше.

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

В комментариях и предлагаемом ответе стало ясно, что существует путаница в терминах stdin и stdout, ошибочно принимая их за stdin и stdout самого bash-скрипта. Не помогло то, что я использовал стандартный ввод для получения пользовательского ввода с клавиатуры, чтобы показать отправку клиентам, а также получение.

Конечно, каждая программа имеет свой собственный взгляд на то, что является стандартным, и в своих статьях я всегда имел в виду только stdout и stdin с точки зрения socat или ncat.

Вот пример того, как может выглядеть готовый код в «псевдокоде»:

while read line
do
   # ...Process line based on it's contents.
   #
   # When a response is needed, stored in a variable
   # called reply, then this might happen:

   if [ -n "$reply" ] ;
   then
      echo "$reply" > socat_stdin
   fi
done < socat_stdout

По пути я пробовал много версий этого:

while read line
do
   echo "$line"
   echo "Back at you: $line" >&3

done < $( exec 3> >(ncat -l -k $port) )

К настоящему моменту я попробовал все способы перенаправления на socatstdin с помощью < и в некоторых конфигурациях он не начинает говорить, что не указано два соединения. Очевидно, я еще не набрал правильную комбинацию.

Вот на чем я остановился вчера вечером:

#!/bin/bash
#
port=7654
inFile=/tmp/in
outFile=/tmp/out
logFile=/tmp/log
exec /bin/socat -U TCP-LISTEN:$port,fork EXEC:"tail -f $inFile" > $outFile 2>$logFile &

while read line
do
   if [ -z "$line" ] ;
   then
      echo "EMPTY LINE!"
   else
      echo "user: $line"
      echo "Back at you: $line" > "$inFile"
   fi
done < $(tail -f "$outFile")

Если бы я мог выяснить, как заставить exec (не EXEC!) выполнять составную команду, я бы хотел попробовать поставить перед ней tail -f и передать ответы от сервера клиенту(ам).

При диагностике этого lsof показывает, среди прочего:

socat   3588549 root    0r   CHR                1,3      0t0         4 /dev/null

...ДА, у него постоянно есть открытый файловый дескриптор /dev/null?! Я думаю, это явный признак «Я делаю это неправильно!» И действительно, в приведенном выше сценарии клиенты отключены. Хм...

Если есть вопросы по этому поводу, просто задавайте и т. д. Спасибо за помощь...

...На данный момент вопрос остается; как это делается?


Работая над этим, я нашел в литературе ссылку на этот файл: /usr/share/doc/socat/mail.sh

Для тех, кому интересно узнать об этом файле, он указывает путь, НО для этого требуются ДВЕ программы или вторая итерация; было бы ЗАМЕЧАТЕЛЬНО исключить один, и я, возможно, хочу адаптировать предложение Филиппа по именованной трубе.

Вот содержимое этого файла — оно довольно короткое:

#! /bin/sh
# source: mail.sh
# Copyright Gerhard Rieger and contributors (see file CHANGES)
# Published under the GNU General Public License V.2, see file COPYING

#set -vx

# This is an example for a shell script that can be fed to socat with exec.
# Its clue is that it does not use stdin/stdout for communication with socat,
# so you may feed the mail message via stdin to the script. The message should
# contain appropriate mail headers without continuation lines.
# socat establishes the connection to the SMTP server; the script performs the
# SMTP dialog and afterwards transfers the message body to the server.
# Lines with only a dot are not permitted - use two dots as escape.
# This script supports multiline answers from server, but not much more yet.

# Usage:  cat message.txt |socat exec:"mail.sh [email protected]",fdin=3,fdout=4 tcp:mail.relay.org:25,crlf

while [ "$1" ]; do
    case "$1" in
    -f) shift; mailfrom = "$1"; shift;;
    *) break;;
    esac
done

rcptto = "$1"
[ -z "$1" ] && rcptto = "root@loopback"
#server=$(expr "$rcptto" : '[^@]*@\(.*\)')
[ -z "$mailfrom" ] && mailfrom = "$USER@$(hostname)"

# this function waits for a complete server message, checks if its status
# is in the permitted range (terminates session if not), and returns.
mail_chat () {
    local cmd = "$1"
    local errlevel = "$2";  [ -z "$errlevel" ] && errlevel=300

    if [ "$cmd" ]; then  echo "> $cmd" >&2;  fi
    if [ -n "$cmd" ]; then echo "$cmd" >&4; fi
    while read status message <&3;
        (
            case "$status" in
            [0-9][0-9][0-9]-*) exit 0;;
            [0-9][0-9][0-9]*) exit 1;;
            *) exit 1;;
            esac
        )
    do :; done
    if [ -z "$status" ]; then  echo smtp connection failed >&2; exit;  fi
    echo "< $status $message" >&2
    if [ "$status" -ge "$errlevel" ]; then
        echo $message >&2
        echo "QUIT" >&4; exit 1
    fi
}


# expect server greeting
mail_chat
#...more calls to the mail_chat function...

В вашем сценарии socat есть стандартный ввод, стандартный вывод и порт, каковы их отношения?

Philippe 03.07.2024 22:27

@Philippe Меня не особенно волнует, ЧТО вы их называете, но с точки зрения процесса bash, используя в качестве ссылки то, что произойдет в реальном интерактивном сеансе с socat, стандартный ввод поступает от человека за клавиатурой, стандартный вывод поступает от клиентов (другие процессы, подключившиеся к порту), и, конечно же, протоколируется stderr, о котором вы не спрашивали. Конечно, чтобы обеспечить быстроту реагирования, должен существовать постоянный цикл чтения того, что приходит от клиентов, а в интерактивном случае это будет «стандартный вывод», выводимый на экран. Затем на это будут действовать и отвечать с использованием «stdin» socat.

Richard T 03.07.2024 22:49

@Philippe Я добавил кое-что, что может дать тебе лучшее представление.

Richard T 03.07.2024 23:03

Если вы перенаправите ввод в socat, то эта сторона будет доступна только для чтения. Поэтому вам следует запускать socat в однонаправленном режиме (-U в этом случае или -u, если писатель и читатель поменялись местами). Однако я не уверен, является ли невыполнение этого требования причиной вашей проблемы.

John Bollinger 03.07.2024 23:10

Итак, socat читает со стандартного ввода и передает прочитанное в порт, читает из порта и выбрасывает прочитанное на стандартный вывод. Верно ли это понимание?

Philippe 03.07.2024 23:22

@JohnBollinger Попробовал и ДА, ПОМОГЛО! СПАСИБО! Это еще не вся история: я делаю что-то не так с частью передачи из bash-скрипта в клиент... Но это улучшение!

Richard T 03.07.2024 23:35

@Philippe Давайте не зацикливаться на ярлыках: выходные данные скриптов должны быть входными данными socat, которые затем поступают в порт. Стандартный выход Socat - ОТ КЛИЕНТОВ, ИСПОЛЬЗУЮЩИХ ПОРТ - нужно попасть в скрипт. И недавнее изменение msot, основанное на вкладе Джона, является шагом в этом направлении — эта часть теперь работает, и клиент не выходит из строя после двух попыток записи.

Richard T 03.07.2024 23:39

@JohnBollinger ... Я был не совсем прав или совсем не прав: с флагом -u НЕТ, данные не попадают в сценарий от клиентов, но теперь они попадают в выходной файл ?! У меня есть хвост -f в моей тестовой среде и еще один в сценарии; последний не получает данные?! Должно быть, что-то простое, я делаю неправильно! Арг!

Richard T 03.07.2024 23:56

Вы пробовали именованные каналы (mkfifo)?

Renaud Pacalet 04.07.2024 06:39

@RenaudPacalet Да, это был аспект одного из примеров, которые я нашел и попробовал, и я сказал, что он работает, но передает управление тому, кто подключается к порту! Однако, внося изменения оттуда, я не увидел, насколько это сильно отличается от использования < и > для файлов, и поскольку файлы имеют преимущество в том, что оставляют след на диске, если хотите, я сейчас нахожусь именно там. Однако МОЖЕТ быть, я (я!) неправильно использовал < и >. ... Через несколько минут я собираюсь немного переработать вопрос, чтобы прояснить ситуацию, и, вероятно, приведу больше примеров того, «где я был до сих пор».

Richard T 04.07.2024 15:24

Просто мысль: в какой-то момент вам может оказаться более продуктивным написать сервер на более функциональном языке, используя потоки и встроенную поддержку сокетов; например, Python, Ruby, Perl и т. д. Скорее всего, вы сможете выразить то, что хотите, с помощью гораздо меньшего количества хакерского кода.

nneonneo 05.07.2024 04:26

@nneonneo Спасибо... Как уже отмечалось где-то в этих комментариях, для этой задачи уже существовал код Bash, написанный задолго до того, как эти более удобные платформы стали доступны. Если бы я знал, что перенести его на ncat/socat будет так сложно, я бы наверняка посвятил время уже начатому - но дольше написанию с нуля - усилиям по Java. Но теперь эти усилия уже улучшили старый код, и он, скорее всего, будет «запущен в производство» сегодня или, может быть, на выходных. ...Переход на socat теперь помогает будущим усилиям, устраняя узкое место в тестировании клиентов.

Richard T 05.07.2024 14:37
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
12
111
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Моя цель - запустить экземпляр socat (предпочтительно) или ncat (также должен работать хорошо) в качестве «сервера» и обрабатывать оба направления двунаправленного трафика - stdin и stdout - сценарием bash.

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

Я могу придумать несколько способов добиться этого, но проще всего будет использовать сопроцесс.

#!/bin/bash

port=7654
logFile=/tmp/log

coproc echo_server { cat; }

/bin/socat TCP-LISTEN:$port,fork STDIO <&${echo_server[0]} >&${echo_server[1]} 2>$logFile

В качестве альтернативы вы можете использовать тип адреса socat или SYSTEM, чтобы подключить его к сценарию. Это было бы проще всего сделать с помощью отдельного скрипта, но не намного сложнее сделать это с помощью всего лишь одного скрипта:

#!/bin/bash

if ! [ $1 = --server ]; then
  port=7654
  logFile=/tmp/log

  exec /bin/socat TCP-LISTEN:$port,fork EXEC:"${BASH_SOURCE[0]} --server" 2>$logFile
else
  # echo server
  cat
fi

Этот вариант не зависит от особенностей оболочки Bash.

Я отметил слово «coproc», упомянутое в других ответах моего исследования, но в МОЕЙ системе его, похоже, нет, поэтому я его проигнорировал. «который coproc» ничего не возвращает. ... Если это буйтин, то я его ПОЛНОСТЬЮ пропустил!

Richard T 04.07.2024 00:13

Спасибо, Джон, попробовал... Первое решение не работает, и я не знаю, как это исправить. В нем говорится: «строка 19: 63: нет такого файла или каталога», а номер строки — это тот, который пытается — но безуспешно — запустить socat. ... Кстати: я не требую использования stdin/stdout и предпочел бы, чтобы они этого не делали. Но я возьму то, что работает, и со временем научусь. А пока объясните, пожалуйста, что здесь пытается сделать coproc? ... И: Почему вызов exec работает, а не напрямую?! ...Я пытаюсь здесь не только научиться, но и решить проблему! ПЛЮС, Я НЕ ПОНИМАЮ, что делает if во втором решении.

Richard T 04.07.2024 00:27

Ни одно из решений не работает так, как указано. ИДК, что мешает запуску socat на первом, так как я не понимаю coproc - пока, полагаю, - но я пошел дальше, и второй тоже не работает: после подключения клиенты сообщают "Разбит канал".

Richard T 04.07.2024 00:55
which coproc ничего не возвращает, потому что coproc — это ключевое слово в bash, например while или if, поэтому это часть языка bash, а не команда, которую which можно было бы найти, просматривая ваш PATH. Он был представлен в bash 4.0, поэтому, если у вас нет более старой версии bash, чем та, которая у вас есть, используйте type coproc вместо which coproc, чтобы вы увидели, что она у вас есть, а затем попробуйте. См. gnu.org/software/bash/manual/html_node/Coprocesses.html и unix.stackexchange.com/a/86372/133219 для получения дополнительной информации о coproc.
Ed Morton 04.07.2024 12:41

@RichardT, прошу прощения, я написал неправильные операторы перенаправления для альтернативы coproc. Я не могу сказать вам, почему версия EXEC у вас не работает, но я бы начал устранение неполадок с помещения кода сервера в отдельный скрипт и присвоения ему EXEC. Вы также можете попробовать SYSTEM вместо EXEC, как уже упоминалось в этом ответе. В любом случае, адреса SYSTEM и EXEC являются основным механизмом socat того, что вы пытаетесь сделать, подключая один конец канала, опосредованного socat, к программе.

John Bollinger 05.07.2024 00:50

@JohnBollinger Спасибо, Джон, извинения с радостью приняты - РАД за всю помощь/обучение... Я начал свой новый ответ на основе примера socat (теперь цитируемого в вопросе), но отвлекся от его завершения, ну, сделав не теряю времени, реализуя код, который мне нужно завершить! Он также использует exec, но вместо stdio, что, как я также отметил в обновлении q, проблематично; использование fd3 и fd4 вместо этого очень помогает, и то, что я делаю сейчас. ...Разочаровано, что разработчики socat не нашли способа сделать это для процесса вызова, учитывая всю его сложность.

Richard T 05.07.2024 01:06

@RichardT, я думаю, ты неправильно понимаешь комментарий в цитируемом тобой сообщении. Насколько я прочитал, смысл не использовать stdin/stdout для связи с socat в этом случае заключается в том, чтобы эти потоки можно было использовать для чего-то другого (получения текста сообщения электронной почты), а не потому, что socat имеет какие-либо особые проблемы с использованием стандартные потоки.

John Bollinger 05.07.2024 01:09

@JohnBollinger (Вы имеете в виду КОД, который я процитировал, а не «сообщение»?) ... О, УВЕРЕН, но меня не волнует, ПОЧЕМУ они хотели это сделать: мое решение использует один набор кода, который может быть/является командой - строко-ориентированный инструмент управления системой, а также daemonize , и у него НЕ обременены stdin и stdout для socat, что позволяет одному сценарию делать все это без особой боли. ... Если бы socat позволил нам объявить fds из родительского процесса, это было бы хорошо и, конечно же, возможно, но они просто этого не сделали. ...Я не плохой bash-программист, просто у меня есть пробелы. В конце концов, я использовал его с конца 1980-х годов! Просто до недавнего времени я никогда не относился к этому слишком серьёзно.

Richard T 05.07.2024 01:16

Попробуйте эту версию:

#!/bin/bash

if test "$1" = server; then
    while read input; do
        echo "Hello $input"
    done
    exit
fi

exec {socat_in}<>  <(true)
exec {socat_out}<> >(true)

socat - EXEC:"./test.sh server" <&$socat_in >&$socat_out &

socat_pid=$!

while read -r -p "Input (Ctrl-D to quit): " line; do
    echo "User input: $line"
    echo "$line" >&$socat_in
    echo "Response from socat:"
    head -n 1 <&$socat_out
done

exec {socat_in}>&-
exec {socat_out}>&-
kill $socat_pid

Бежать с :

chmod +x test.sh
./test.sh

Эм... как к клиентам подключиться? Упс! Вы пробовали это? Вся причина использования socat или ncat (netcat) — получение ввода-вывода от удаленных клиентов. ... Исходный код еще до того, как я начал, уже использовал файлы, смонтированные по nfs, и эта работа заключается в том, чтобы перенести этот древний код «в современную эпоху» и сделать его гораздо более гибким - например, NFS не нужен. ...Моя сейчас цель - проанализировать довольно загадочные правила синтаксиса socat, чтобы найти способ указать файловые дескрипторы сценария вызова. Вызов FD дочернего сценария, запускаемого socat, является вторым лучшим вариантом...

Richard T 04.07.2024 20:04

Кстати, запрос инициатора серверной части был лишь временной удобной функцией для генерации текста для ответа клиентам. И в первой версии, которую я выложил, была ошибка. Но в любом случае я переписал вопрос; вы могли бы взглянуть по-новому. ... А еще СПАСИБО за работу над этой проблемой!

Richard T 04.07.2024 20:05

@JohnBollinger Филипп, Джон, обратите внимание на обновление вопроса с кодом ОТ команды разработчиков socat — стоит посмотреть!

Richard T 04.07.2024 20:15
Ответ принят как подходящий

Действительно, разработчики socat предоставили важную подсказку, которую, однако, трудно найти! - с их файлом, включенным в мой дистрибутив:

/usr/share/doc/socat/mail.sh

Покопавшись и попробовав, я получил что-то вроде этого:

ONE скрипт использует функции следующим образом:

function startSocat()
{
   # ... check for free port, etc
   socat exec:"${BASH_SOURCE[0]} -SD",fdin=3,fdout=4 TCP-LISTEN:$port,fork
   # -SD indicates, "socat-daemon"
   # 
   # The above tells it to start this same code, pass it -SD and connect to it
   # using file descriptors 3 and 4 and, of course where to listen, etc.
}

function socatLoop
{
   while read line <&3;
   do
      # Process the read in line - in variable line!
   done
}

function replyToClient()
{
   echo "$@" >&4
}

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

  1. Любой процесс принятия решения, который вам подходит, может вызвать startSocat(). После вызова мы не ожидаем, что он вернет нам управление, поскольку socat не находится в фоновом режиме. Если да, то перед выходом вызовите wait.\
  2. После запуска демон должен знать, что он запустился, и в этом вся суть флага -SD. Вместо этого умный программист может определить, выполняется ли процесс в интерактивном режиме или нет, и т. д. Это задание остается на усмотрение читателя. Однако, как только станет известно, что это демон, вызовите socatLoop.
  3. Отсюда socatLoop необходимо определить, как себя вести, и, при необходимости, вызвать ответToClient, который отправляется всем подключенным клиентам.

Конечно, если вы хотите организовать приватный диалог с отдельным клиентом, это потребует больше усилий с вашей стороны, но теперь, когда вы знаете основы, это не так сложно!

Небольшая придирка — function funcname() { сочетает в себе устаревший синтаксис ksh funcname { и синтаксис стандарта POSIX funcname() { таким образом, что это совершенно несовместимо ни с устаревшим ksh, ни с POSIX sh.

Charles Duffy 06.07.2024 21:22

@CharlesDuffy -chuckle- Это скорее случайность публикации, чем что-либо еще. Я обычно объявляю функции как: function fname() { и т. д., и причина прагматична: я могу в любой момент найти определение функции (в vi), используя либо /ion <start-of-f-name>, либо /< end-of-f-name>(, в зависимости от того, что кажется проще. Кстати, я УВЕРЕН, потратил на это много времени! Я ожидал, что socat будет вести себя как большинство других процессов, но даже такие вещи, как именованные каналы, не сработали - я продолжал думать, что я делал это неправильно, но теперь я в этом не уверен... Как бы то ни было, у меня все работает. Спасибо за ответ!

Richard T 07.07.2024 04:56

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