Reputation: 28500
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.
Upvotes: 4
Views: 139
Reputation: 70987
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!!
Upvotes: 2
Reputation: 12255
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.
Upvotes: 3