I open one terminal, and execute
socat TCP-LISTEN:12345,fork -
then I open another terminal and execute
socat TCP-CONNECT:127.0.0.1:12345 EXEC:'echo ciao'
which results in printing one line containing ciao in the first terminal.
So far so good, but what if I want to execute a script written inline?
I thought I could do like this in the second terminal:
socat TCP-CONNECT:127.0.0.1:12345 EXEC:'bash -c "echo ciao"'
but this results in the first terminal only receiving a newline character.
What am I missing? Where are the 4 characters ciao getting lost?
Is it just me misunderstanding how the shell is supposed to work in this circumstance, or does the observed behavior depend on how socat API is designed?
I know I can put the Bash script in a file, make it an executable available on the PATH, and use EXEC:that-script.
I've also understood, reading the manual, that there's the SHELL for of address, so one can do:
socat TCP-CONNECT:127.0.0.1:12345 SHELL:"for ((i=0;i<10;i++)); do echo ciao; sleep 1; done"
but I'm still curious as to what's wrong with the bash -c "stuff" syntax.
I did some experiments a while ago using strace to see what got passed on to the exec() system call, for SYSTEM:.
In summary, it
\" with no change\n as \n\n:execve("/bin/sh", ["sh", "-c", "the command and args"], env)For EXEC: it seems similar, but finishes by splitting the string on
spaces into words, and passing the words as the argv array, with the first
word also being used as the program to exec.
So something like SYSTEM:"dd 'conv=sync bs=2'" (where the 2 args should
(wrongly) stay as one string) becomes ok:
execve("/bin/sh", ["sh", "-c", "dd conv=sync bs=2"], env)
and EXEC:"dd 'conv=sync bs=2'" becomes
execve("/usr/bin/dd", ["dd", "conv=sync", "bs=2"], env)
The following shows what string is passed to execve() for various examples:
SYSTEM:'set -v;echo "abc";'
"set -v;echo abc;"
SYSTEM:'set -v;echo \"abc\";'
"set -v;echo \"abc\";"
SYSTEM:"set -v;echo 'abc';"
"set -v;echo abc;"
SYSTEM:'echo abc\ndef # <- real newline
echo pqr def' # <-tab char
"echo abc\ndef\necho pqr\tdef"
SYSTEM:"echo abc\ndef # <- real newline. (as above with "")
echo pqr def" # <-tab char
"echo abc\ndef\necho pqr\tdef"
The sources are
xio-exec.c
with the call to nestlex(), which is
here.
I haven't tried to follow the code too much, but my tests seem coherent
with the comments there.
When I speak about 1st window, I speak about another terminal window in which you've been run:
socat TCP-LISTEN:12345,fork -
EXEC:"bash -c '...'" as requestedYes, you could do this (based on Philippe's comment):
command="for ((i=0;i<5;i++));do printf '%4d %s\n' \$i ciao;sleep .25;done"
Test:
bash -c "$command"
0 ciao
1 ciao
2 ciao
3 ciao
4 ciao
with 250 milliseconds between each lines...
EXEC:
socat TCP-CONNECT:127.0.0.1:12345 EXEC:"bash -c \"${command//[\' ]/\\\\&}\""
Will do the job!
But this
") in your code and you may encounter some pain debugging your command string!socat's EXEC...socat, with SYSTEM:You have to write POSIX shell code!! You could use bash for overall strings manipulation (putting function to string by using declare -f), so confine your script in a function, but respecting POSIX syntax!
Here is a sample using shared variable ($somevar) and some arguments to the function.
someFunc() {
i=0
while [ $i -lt ${2:-5} ]; do
printf ' - %2d %s %s\n' $i "$1" "$somevar"
sleep ${3:-.3}
i=$((i+1))
done
}
somevar='baz boo.'
socat TCP-CONNECT:127.0.0.1:12345 SYSTEM:"$(declare -f someFunc
);somevar=${somevar// /\\\\ } someFunc Foo\\\\ bar 8 .5;"
Then you will see in 1st window, with an interval of an half second on each line:
- 0 Foo bar baz boo.
- 1 Foo bar baz boo.
- 2 Foo bar baz boo.
- 3 Foo bar baz boo.
- 4 Foo bar baz boo.
- 5 Foo bar baz boo.
- 6 Foo bar baz boo.
- 7 Foo bar baz boo.
testMyCmd() { ${2:-busybox sh} -c "$(echo -e "$1")" ;}
Then
testMyCmd "$(declare -f someFunc);somevar=${somevar// /\\\\ } someFunc Foo\\\\ bar 4 .2" dash
- 0 Foo bar baz boo.
- 1 Foo bar baz boo.
- 2 Foo bar baz boo.
- 3 Foo bar baz boo.
someFunc() {
read -p 'Enter a number: ' i
l=0
while [ $i -gt 0 ]; do
i=$((i-1))
l=$((l+1))
echo Loop $l.
sleep .2
done
}
socat TCP-CONNECT:127.0.0.1:12345 SYSTEM:"$(declare -f someFunc
);someFunc;",pty,stderr
This will prompt in 1st window: Enter a number: .
If you hit a number in 1st window, command will continue and exec requested number of loop.
Under bash; you don't need to use netcat or socat for this! You could use pseudo /dev/tcp network folder by:
for ((i=0;i<10;i++)); do echo ciao; sleep 1; done >/dev/tcp/127.0.0.1/12345
someFunc() {
printf 'Enter a number: '
read i
l=0
while [ $i -gt 0 ]; do
i=$((i-1))
l=$((l+1))
echo Loop $l.
sleep .2
done
}
someFunc >/dev/tcp/127.0.0.1/12345 <&1 2>&1
script:This use script which is not bash, but more commonly installed on Linux systems than socat.
someFunc() {
read -p 'Enter a number: ' i
l=0
while [ $i -gt 0 ]; do
i=$((i-1))
l=$((l+1))
echo Loop $l.
sleep .2
done
}
script -qf /dev/null -c "$(declare -f someFunc
);someFunc" >/dev/tcp/127.0.0.1/12345 2>&1 <&1
This way is clearly the most robust, as socat EXEC was done for this kind of use!
But as you are already writting your own script, maybe are you trying to avoid multiple files!!
For this, you could prepare your script to be able to call himself as socat executable:
#!/bin/bash
someFunc() {
local _i _l _str=${1:-Some string}
read -p 'Enter a number ' -r _i
for ((_l=1;_l<=_i;_l++)){
printf ' - %2d %s\n' $_l "$_str"
sleep .25
}
}
otherFunc() {
local a
for a; do
[[ -f /proc/$a ]] &&
printf '%s %-12s: %s\n' $HOSTNAME $a "$(</proc/$a)"
done
}
rbash() { bash -i ;}
if [[ $1 == doIt ]]; then
shift
"$@"
exit 0
fi
if [[ $1 == --script ]] && [[ $2 ]] && declare -f $2 &>/dev/null; then
shift 1
script -qf /dev/null -c "$0 doIt ${*// /\\ }" \
>/dev/tcp/127.0.0.1/12345 2>&1 <&1
elif [[ $1 ]] && declare -f $1 &>/dev/null; then
socat TCP-CONNECT:127.0.0.1:12345 EXEC:"$0 doIt ${*// /\\\\ },pty,stderr"
else
echo "Command '$1' unknown."
fi
Save this in a script file named sendMyFunc, (give him execution rights: chmod +x sendMyFunc, then
./sendMyFunc.sh someFunc "Foo bar"
Will prompt Enter a number in 1st window,
3
- 1 Foo bar
- 2 Foo bar
- 3 Foo bar
and do the job.
./sendMyFunc.sh otherFunc uptime loadavg
Will print two lines in 1st window:
springfield loadavg : 2.30 1.92 1.86 1/1922 2695597
springfield uptime : 5987838.21 17133210.42
./sendMyFunc.sh --script otherFunc uptime loadavg
Will do same, but using script instead of socat.
./sendMyFunc.sh --script rbash
Use it at your own risk!!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With