bug-bash
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Need help for bash function return value


From: Greg Wooledge
Subject: Re: Need help for bash function return value
Date: Fri, 12 Oct 2012 08:53:25 -0400
User-agent: Mutt/1.4.2.3i

On Thu, Oct 11, 2012 at 08:01:40PM -0700, Tinni wrote:
> I am  very new to the shell scripting.. Need some pointers for getting the
> return value in shell scripting.

"Return value" is an integer from 0 to 255.  That's not what you want.

> I want the values (  db_host_user, db_host, ORACLE_SID)  should be returned
> from the clonesel to test1.sh
> 
> If i set the  script inside  the test1.sh  as  
> 
> val=`ssh remoteserver '/bin/sh clonesel' `,    

So, you're trying to retrieve 3 separate (string?) values from a remote
system.

> In the remote server , the script  clonesel is as follows:
> 
> 
> #!/bin/bash
> 
> echo
> echo -e "\e[1;31m\tSetup Oracle Environments \e[0m"
> echo -e "\t--------------------------"

All of that extraneous output and terminal formatting is going to get
in the way.  Your best bet would be to write a separate script and
run that instead of running this clonesel thing.

> echo
> echo
> echo -en "\e[1;32m\tType DB OS User Name :\e[0m"
> read db_host_user

Wait... clonesel is interactive?  You were planning to run an interactive
script on a remote system to prompt the user for information and then
send that information back to the calling script on the local system?
That doesn't make much sense.  Why not simply prompt the user for the
information on the local system and skip the ssh call altogether?

(Another problem you had is that the clonesel script contains bash-specific
code, and even begins with a #!/bin/bash shebang, but you explicitly
invoked it using /bin/sh.)

-----------------------

Let's suppose you were starting from scratch and didn't have all of this
crazy code in place, and you wanted to retrieve three string values from
a remote system.  This is perhaps a completely different problem from the
one you asked, but it might be helpful if you can rephrase your original
problem a bit.

Now, a script can read streams of data, and you can embed three strings
into a single stream of data.  The trick is to be able to separate them
into the component strings on the receiving end.  The most common way
to do that is to use some sort of delimiter between the strings.  The
delimiter must be a character, or a sequence of characters, which is
guaranteed not to appear within the strings.

Let's assume that we can use a newline character as the delimiter.

Then, you could do it this way:

#!/bin/bash

{
  IFS= read -r string1
  IFS= read -r string2
  IFS= read -r string3
} < <( ssh "$user"@"$host" bash <<'EOF'
source ~oracle/.whatever || exit
echo "$thing1"; echo "$thing2"; echo "$thing3"
EOF
)

Obviously you would substitute the appropriate variable names, and the
appropriate filenames or whatever steps you need to take to get the
remote shell into a state where it can produce the information you need.

I realize that's probably a huge number of new features all at once, so
let me break this down into pieces and explain it, and why it's done this
way.

The first part, you already know about:

#!/bin/bash

This ensures that our script will be run under bash, and not under sh.
This is necessary because we are using bash syntax that will not work
under sh.  This also means that the script must be invoked as ./script
or as "bash script", NOT as "sh script" or "/bin/sh script".

The second part is a command grouping:

{
  command one
  command two
  ...
}

This grouping means that any redirections applied to the group are
inherited by all of the commands in the group.  It also *does not*
use a subshell.  If we had used parentheses instead of curly braces
then bash would have created a subshell for the commands, and the
read commands would have been performed inside that subshell.  That
is not what we want, because when the subshell exits, all the variables
set by all the read commands will be gone.

Next we have several read commands:

IFS= read -r string1
IFS= read -r string2
IFS= read -r string3

Each of these reads one line of data from stdin (terminated by a newline
character), and puts the contents of that line into the variable named
after the -r.  The "IFS=" part ensures that there will be no stripping
of leading/trailing spaces.  The "-r" part ensures that there will be no
special handling of backslash characters in the data.  We just want the
actual payload, with NO interpretation by the shell, and both IFS= and -r
are required to ensure that.

Because the reads are grouped together, they all share a single input.
The first one will read a line from that input, and then the second one
will read the NEXT LINE from that input, and so on.

Next we have a redirection which is applied to a group:

{
  ...
} < foo

The "<" character by itself means that stdin (standard input) is being
redirected from foo.  The placement after the closing "}" of the group
means that the redirection applies to the entire group.

Next we have a process substitution:

<( some command )

This is a special bash feature (and the reason we needed #!/bin/bash).
It creates a virtual filename that can be used as an argument to a program
or a target of a redirection.  We're using it as the target of a redirection,
which is why we have this:

< <( some command )

There MUST be a space between the redirection "<" and the process
substitution "<(".  You don't actually need spaces between "<(" and
the command, or between the command and ")".  Those were just inserted
for clarity.

Next we have an ssh command:

ssh "$user"@"$host" command

This causes "command" to be executed on the remote host "$host" as user
"$user".  The details of ssh are beyond the scope of this mailing list,
so hopefully you know the basics there.  In our case, "command" is bash.
We are telling ssh to run bash on the remote system.  That instance of
bash will read commands from its own stdin (since we didn't give it any
arguments).

Next we have a here document:

ssh ... <<'EOF'
...
EOF

This is a special kind of redirection which creates stdin for a command
using lines of text from the script itself, rather than from a separate
file.  The payload which is sent to the command's stdin will be the
lines of text in between <<'EOF' and EOF.

Since we have 'quotes' around 'EOF', the script will NOT perform any
substitutions within those lines.  That's what we want, because we want
the lines of text to be interpreted on the REMOTE system, not on the
local system.

Inside the here document, we have the commands that the remote bash will
execute for us:

source ~oracle/.whatever || exit
echo "$thing1"; echo "$thing2"; echo "$thing3"

Now, here I am assuming a thing which may not be true in your case.
I am assuming that the variables you require can be instantiated within
a bash shell by sourcing some file in a fixed location.  If that isn't
true for you -- if you really must prompt a user for them -- then you
should throw away ALL of the code you have, and throw away this email
as well, and just start from scratch, and prompt the user for the data
on the local system.

But if there is such a file on the remote system (perhaps ~oracle/.profile),
then the remote bash will source it (will read the commands inside it and
execute them as if they had been typed by a user), and then the variables
we require will become available.

We use three separate echo commands to write the variables to stdout.
Echo adds a newline at the end -- again, that is what we want, because
we designed this so that there would be newline delimiters at the end of
each variable, because that is what "read" expects.

The remote bash will write a single stream of data to its stdout.  This
stream will be a variable, then a newline, then another variable, then
a newline, then the third variable, then a newline.

The process substitution + redirection takes stdout from the command(s)
inside <(...)  and makes it available as stdin to the group of read
commands.  So, the three reads will see that data stream (three variables
with newlines) and will operate on that.

Once this chunk of code has finished executing, you will be left with
three shell variables on the local system, containing the three pieces
of data you wanted from the remote system.  Then it's up to you to do
whatever's necessary with those data.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]