Source From Standard In (Bash on OSX) - bash

I am trying to do something like this
ruby test.rb | source /dev/stdin
where test.rb just prints out cd /. There are no errors, but it doesn't do anything either. If I use this:
ruby test.rb > /tmp/eraseme2352; source /tmp/eraseme2352
it works fine, but I want to avoid the intermediate file.
Edit: The whole point of this is that the changes need to persist when the command is done. Sorry I didn't make that clearer earlier.

You can try:
$(ruby test.rb)
$(...) tells bash to execute whatever output is produced by command inside ().

eval `ruby test.rb`

The following code works for me on Mac OS X 10.6.7:
/bin/bash
sw_vers # Mac OS X 10.6.7
bash --version # GNU bash, version 3.2.48(1)-release (x86_64-apple-darwin10.0)
source /dev/stdin <<<'echo a b c'
source /dev/stdin <<< "$(ruby -e 'puts "echo a b c"')"
source /dev/stdin <<<'testvar=TestVal'; echo $testvar
source /dev/stdin <<-'EOF'
echo a b c
EOF
source /dev/stdin <<-'EOF'
$(ruby -e 'puts "echo a b c"')
EOF
(
source /dev/stdin <<-'EOF'
testvar=TestVal
EOF
echo $testvar
)

Until a more experienced bash hacker comes along to correct me, you could do this:
for c in `ruby test.rb` ; do $c ; done
Caution: This doesn't do what you want. Read the comments!

bash (not sh):
while read -a line
do
"${line[#]}"
done < <(somescript)
Spaces in arguments to commands will need to be backslash-escaped in order to work.

How about just
ruby test.rb | bash

Related

Strange behaviour for echo with -e flag passed to bash with -c flag

I cannot understand the behaviour of this bash script (which I cut it out of a longer real use case):
# This is test.sh
cmd="echo -e \"\n\n\n\t===== Hello World =====\n\n\""
sh -c "$cmd"
What it prints is:
$ ./test.sh
-e
===== Hello World =====
$
If I remove the -e flag, everything is printed correctly, with quoted chars correctly interpreted and without the '-e' spoil: but it shouldn't be like that.
My bash is: GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17), under macOS.
In Posix mode (when run as sh), bash 3.2's echo command takes no options; -e is just another argument to write to standard output. Compare:
$ bash -c 'echo -e "a\tb"'
a b
$ sh -c 'echo -e "a\tb"'
-e a b
A literal tab is printed in both cases because Posix echo behaves the same as bash echo -e.
For this reason, printf is almost always better to use than echo to provide consistent behavior.
cmd='printf "\n\n\n\t===== Hello World =====\n\n"'
sh -c "$cmd"
sh-4.2# cat test.sh
cmd="echo -e \"\n\n\n\t===== Hello World =====\n\n\""
sh -c "$cmd"
sh-4.2# ./test.sh
===== Hello World =====
sh-4.2#
It is getting printed correctly on my machine
OK, I think I found it myself, from here:
sh, the Bourne shell, is old. Its behaviour is specified by the POSIX standard. If you want new behaviour, you use bash, the Bourne Again shell, which gets new features added to it all the time. On many systems, sh is just bash, and bash turns on a compatibility mode when run under that name.
Groan...

How can I save and restore TTY settings from a subshell? (Or: understanding pty/tty within subshells)

I am trying to understand stty's operation within a subshell. Here is a small Ruby script I am working with to wrap my head around the behavior. I'm just trying to save and restore the tty state after we exit an infinite loop via ^C.
original_tty = `stty -f /dev/tty -g`
begin
loop do end
rescue Interrupt
# Suppress exception
ensure
$stderr.puts `stty -f /dev/tty #{original_tty}`
$stderr.puts "stty exits with: #{$?}, i.e. success == #{$?.success?}"
end
Now, if you put this into a file wut.rb, when I run it at my command line with bash -c 'echo | ruby wut.rb it works; i.e. stty returns 0. But if I run it in a subshell via command substitution, bash -c 'echo $(echo | ruby wut.rb)', it does not; i.e. stty returns 1.
Running these directly from the command line works as I might expect:
$ foo=$(stty -f /dev/tty -g)
$ echo $foo
gfmt1:cflag=4b00:iflag=6b02:lflag=200005cf:oflag=3:discard=f:dsusp=19:eof=4:eol=ff:eol2=ff:erase=7f:intr=3:kill=15:lnext=16:min=1:quit=1c:reprint=12:start=11:status=14:stop=13:susp=1a:time=0:werase=17:ispeed=38400:ospeed=38400
$ echo | stty -f /dev/tty $foo
$ echo $?
0
$ echo $(echo | stty -f /dev/tty $foo)
$ echo $?
0
I'm on Mac OS X 10.10.2 using bash 3.2 and ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14] if it matters…
Why is this? I think it has to do with the subshell created by command substitution not being/having a tty, but I'm not sure. I would very much appreciate:
any explanation of what's going on (including good readings on PTY/TTY—so far "A Brief Introduction to Termios" and "The TTY demystified" have been the most helpful)
and suggestions as to how I might successfully restore the original tty settings within a command substitution.
Thanks!

Using backticks and pipes in Ruby

This bug appears when using shell commands in a ruby script. The commands are not executed as what would happen in the bash terminal.
The first two commands are reasonable, the third is not.
`echo foo` # => "foo\n"
`echo -n foo` # => "foo"
`echo -n foo | cat` # => "-n foo\n"
In bash, we would have the following:
$ echo foo # => "foo\n"
$ echo -n foo # => "foo"
$ echo -n foo | cat # => "foo"
Is there a way I am messing up the parameter passing to the calls to echo in the Ruby commands? If you're unfamiliar, the command echo returns the string it is given. Without the -n flag, it appends a newline character, when you add the -n, it does not. cat repeats whatever it is given.
First, Ruby uses /bin/sh, not bash, for the back tick operator. It is likely that on your system, /bin/sh is a link to /bin/bash, but bash behaves differently when invoked as sh, for better POSIX compliance. However, it's not perfect, as your example shows. The POSIX specification states that echo "shall not support any options", but leaves handling of an initial argument -n to be implementation-specific.
In a quick test with /bin/sh as a link to /bin/bash,
bash -c "echo -n foo" and bash -c "echo -n foo | cat" produced identical, newline-free results, but sh -c "echo -n foo" and sh -c "echo -n foo | cat" showed the results you report. (I am not sure how other shells, such as ksh, dash, or zsh, behave when invoked as sh, but by default they all treat -n as a newline suppressor.)
For predictable results, use printf instead, which never prints a newline unless a \n is included in the format string.
printf 'foo\n'
printf 'foo'
printf 'foo' | cat
UPDATE: This appears to be a bug in bash 3.2 that was fixed at some point in the 4.x series. With 4.1 or later, sh -c "echo -n foo | cat and sh -c "echo -n foo" produce the same output.

"source" in ruby subshells

I need to run a shell command from a ruby application. I'm using system() but this also applies to backticks.
When running my command, I need to load a shell script first that sets up some things so I try something like this:
system("source my_script.sh && my_command")
On my Mac laptop this works as intended but on my ubuntu server I get:
sh: 1: source: not found
I was wondering about the "sh" in there since my shell should be a bash, so tried this:
system("echo $SHELL && source my_script.sh && my_command")
Which gives me:
/bin/bash
sh: 1: source: not found
So, it is using the right shell but for some reason, source does not work.
Why? And what can I do about it?
Update
As Sergio Tulentsev pointed out, Ruby does not necessarily use the shell that is set in $SHELL.
This gave me the actual shell that ruby was using:
system("ps -p $$ | tail -1 | awk '{print $NF}'")
sh
=> true
So, it's using sh. Can I somehow force it to use bash?
You need to try adding ./ in front of the file you want to source, that should work if the subshell is bash (check $SHELL).
irb(main):003:0> system("source ./test.sh && echo $TEST && cat test.sh")
test
export TEST=test
=> true
If $SHELL is sh, then you need to do . ./test.sh instead of source ./test.sh, as the source keyword is bash only.
Or you can make sure that you are using bash, by doing:
irb(main):007:0> system("/bin/bash -c 'source ./test.sh && echo $TEST && cat test.sh'")
test
export TEST=test
=> true
As others have pointed out, Ruby uses sh for its subshells. One way to make it use bash would be something like system("/bin/bash -c '...'") which leads to all kinds of escaping problems. Instead I decided to use Open3 to spawn a "real" process, run bash in it and pipe my commands into it. Works like a charm:
require "open3"
# using bash --login to ensure the same env as usual
Open3.popen3('/usr/bin/env bash --login') do |stdin, stdout, stderr, wait_thr|
pid = wait_thr[:pid]
stdin.puts("cd some_directory")
stdin.puts("source some_script")
stdin.puts("some_command")
# don't forget to close it again
stdin.puts("exit")
# for debug purposes
stdout.each_line do |line|
puts "STDOUT: " + line
end
stdin.close
stdout.close
stderr.close
end
This may seem like a little overkill but the control it allows over the child process is actually pretty nice.
Thanks everybody for your suggestions.

echo -e option in ubuntu doesn't work

My colleague use Ubuntu and I use openSUSE, we compiled same source code using same makefile, my environment works well, but my colleague can't, always output the can't recognized -e option. We check the makefile, ONLY find echo command use -e option.
Dose Ubuntu's echo function is different with others?
+++++update
in makefile, the echo define as:
TOOLSDIR =
ECHO = $(TOOLSDIR)echo -e
The $(TOOLSDIR) is dir for tool, this make file can detect compile env, if linux or has CYGWIN:
$(TOOLSDIR)is empty, if windows, it will goes to WIN32 version echo tool dir, like:
TOOLSDIR = $(subst /,\,$(MAKEDIR)/tools/WIN32/)
after we execute make, ubuntu will output:
Fatal error: L3900U: Unrecognized option '-e'.
however, openSUSE doesn't have this error
+++++update for echo -e
I write a test makefile in ubuntu
all:
echo -e hello
it output:
echo -e hello
hello
+++++update makefile test in openSUE (12.1) and ubuntu (12.04)
all:
echo -e "hello 1"
echo -e hello 2
echo -e he\nllo3
echo -e "he\nllo4"
opensuse, the output is:
echo -e "hello 1"
hello 1
echo -e hello 2
hello 2
echo -e he\nllo3
henllo3
echo -e "he\nllo4"
he
llo4
in ubuntu, the output is:
echo -e "hello 1"
-e hello 1
echo -e hello 2
hello 2
echo -e he\nllo3
henllo3
echo -e "he\nllo4"
-e he
llo4
Meanwhile, i test in ubuntu, echo without -e, the result is same as in openSUSE, echo with -e
It could depend from the shell you guys are using (echo is often implemented inside the shell)
this is what happens in OS X (Mountain Lion):
$ bash
bash-4.2$ echo -e foo
foo
bash-4.2$ sh
sh-3.2$ echo -e foo
-e foo
The same for OpenSUSE, and Ubuntu.
-e option is not compliant to POSIX (I'm not sure, but it should be the default behavior), btw, to print formatted text, printf is more appropriate command.
From the information you provide, it looks to me that your makefile has a bug (portability issue).
On OSX man echo doesn't list the -e option too, btw. On Ubuntu 12.10 the -e is present on my computer, on my router BusyBox v1.13.4 also accepts the -e (it's internal command for busybox)
But probably it's just the /bin/sh using internal command and ignoring it.
==== UPDATE:
In your makefile remove the $(TOOLSDIR) in front of echo:
ECHO = echo -e
in fact if you specify the path of the echo (i.e. /bin/echo) you force the shell to execute the program from the filesystem instead of the one implemented internally by the shell:
$ echo -e foo
foo
$ /bin/echo -e foo
-e foo
I also find it may cause by ubuntu link /bin/sh to dash but NOT bash. After I use bash, problem solved and there is an other question related to this, which I asked:
same shell script has different behaviou on different Linux distribution
Both Luigi R. Viggiano and How Chen provide good explanations to the problem and solutions to it. In fact, their answers complement each other.
In dash, the echo command does not accept the -e flag. However, in bash, it does.
In Ubuntu, one might consider using update-alternatives to select bash, rather than dash, as the default shell. The following commands will do the trick (warning, these commands will apply system-wide):
sudo update-alternatives --install /bin/sh sh /bin/bash 0
sudo update-alternatives --install /bin/sh sh /bin/dash 0
sudo update-alternatives --set sh /bin/bash
The \ is the escape character in the shell, so \n is just that, an "n", so escape the escape \\\n.
[sg#Study ~]$ echo -e \\nfoo\\nfoo
foo
foo
[sg#Study ~]$
or use quotes
[sg#Study ~]$ echo -e "\\nfoo\\nfoo"
foo
foo
[sg#Study ~]$

Resources