Bash Scripting - shell command output redirection - bash

Can someone help explain the following:
If I type:
a=`ls -l`
Then the output of the ls command is saved in the variable a
but if I try:
a=`sh ./somefile`
The result is outputed to the shell (stdout) rather than the variable a
What I expected was the result operation of the shell trying to execute a scrip 'somefile' to be stored in the variable.
Please point out what is wrong with my understanding and a possible way to do this.
Thanks.
EDIT:
Just to clarify, the script 'somefile' may or may not exist. If it exsists then I want the output of the script to be stored in 'a'. If not, I want the error message "no such file or dir" stored in 'a'

I think because the shell probably attaches itself to /dev/tty but I may be wrong. Why wouldn't you just set execute permissions on the script and use:
a=`./somefile`
If you want to capture stderr and stdout to a, just use:
a=`./somefile 2>&1`
To check file is executable first:
if [[ -x ./somefile ]] ; then
a=$(./somefile 2>&1)
else
a="Couldn't find the darned thing."
fi
and you'll notice I'm switching to the $() method instead of backticks. I prefer $() since you can nest them (e.g., "a=$(expr 1 + $(expr 2 + 3))").

You can try the new and improved way of doing command substitution, use $() instead of backticks.
a=$(sh ./somefile)
If it still doesn't work, check if somefile is not actually stderr'ing.

You are correct, the stdout of ./somefile is stored in the variable a. However, I assume somefile outputs to stderr. You can redirect that with 2>&1 directly after ./somefile.

Related

How to pass argument to a shell command in shell script from terminal

i am writing a shell script practice.sh. I want to give my first argument $1 from command line to ls command in script.e.g
if I run my script in terminal $bash practice.sh *.mp3
the argument *.mp3
I want to use for ls command
#!/bin/bash
output=$ls $1
it doesn't work
any help?
The obvious answer for what you say you want is just
#!/bin/bash
ls "$1"
which will run ls, passing it (just) the first argument to the script.
However, you also say you want to run this like: practice.sh *.mp3 which runs the script with many arguments (not just one) -- the *.mp3 will be expanded to be all the of the .mp3 files in the current directory. For that, you likely want something more like
#!/bin/bash
ls "$#"
which will pass all of the arguments to your script (however many there are) to the ls command.
These scripts will just run ls with its stdout connected to whatever your script has its stdout connceted to, so the output will (likely) just appear on your terminal. If you instead want to capture the output of the ls command (so you can do something else with it), you need something like
#!/bin/bash
output=$(ls "$#")
which will run ls with all the arguments, and capture the output in the variable $output. You can then do things with that variable.
Use shell expansion to record the output of the command in the variable output:
output=$(ls $1)
This will record the output of the command ls $1 in the variable output.
You can then use echo $output to print out your output.
You can read more about shell expansion in the GNU Bash reference manual.

Bash: Assign command to a variable

Below I have an example which confuses me a bit, any help would be appreciated.
I bind a normal command line command (ls) to a new variable. If I echo it it the output is just the command (ls) but if I just use the variable without echo i get the result of the command but why?? Is it because $listdir gets translated to ls so I just get the output? And if I use the echo command it will be interpreted as a string?
router#test:~/scripting$ listdir=ls
router#test:~/scripting$ echo "$listdir"
ls
----- VS ----
router#test:~/scripting$ $listdir
basicLoop.sh fileflood.sh .......
Thank you for any help!
By doing listdir=ls you literally assign a string "ls" to the $listdir variable. So if you run echo $listdir now it will just expand into echo ls, which (as you may have guessed) will just print "ls" onto a screen. If you want to store a result of a command into a variable you can wrap the command in `` or $() (eg. listdir=$(ls) or listdir=`ls`).
jarmusz#emacs~$ listdir=`ls`
jarmusz#emacs~$ echo "$listdir"
dls
docs
music
...
If you want to store just a name of the command and run it later you can do it like this:
jarmusz#emacs~$ listdir=ls
<some other commands...>
jarmusz#emacs~$ echo `$listdir`
dls docs music ...
In this example, echo `$listdir` will expand into echo `ls` and then into echo dls docs music...
When bash is interpreting the commands you feed to it, the first thing it will do is expand any expansions it is given. So when you give it $listdir by the time bash starts to execute the value it is given, all it knows is that it was given the value ls. It does not care where the value came from, only what the value is.
Lets look at the trace given after running set -x, which instructs bash to prints to stderr after expansion and before execution:
$> echo $listdir
+ echo ls
ls
$> $listdir
+ ls
file_0 file_1
As you can see, in the second line, bash will attempt to run the command ls just as if you have explicity called ls or even /usr/bin/ls
Edit
Expansion isn't the first step in in shell evaluation, see #Gordon Davisson's comment for details

Why am I not saving the result of the shell command into my variable?

I've done this before.
I've read numerous posts in this forum, and googled even more on how to save the result of a shell command into a variable. All of them say to do this
VAR="$(shell_command)"
echo $VAR
or
VAR=`shell_command`
echo $VAR
But I'm trying to do this
VAR="$(python2.7 -V)"
echo "Version is $VAR"
or
VAR=`python2.7 -V`
echo "Version is $VAR"
and I see
Python 2.7.14
Version is
IOW I'm not storing the result? Why is this? I just want pure bash and want to understand why it doesn't do what I expect it to do. Thanks!
In this particular case, it's because Python prints the version to its standard error stream. The $(...) construction (or backticks) only captures what the given command sends to standard output.
You can get around this, in this case, by writing $(python2.7 -V 2>&1). Here 2>&1 is shell code that means "replace the standard error stream with a copy of the standard output stream", so anything Python thinks it's writing to standard error actually goes to the destination that standard output goes to.
Note that in some cases, similar problems can result from improper use of quotes. In general it's a good idea to surround command substitutions with double quotes:
VAR="$(python2.7 -V 2>&1)"
It turns out not to matter in this case though.
Try this:
python2.7 -V >/dev/null
And you'll still see the output, which means that the version info is not sent to standard output (stdout).
And this:
python2.7 -V 2>/dev/null
The output is gone, further confirming that it's sent to standard error.
So you would like to do this:
VAR="$(python2.7 -V 2>&1)"
# ^^^^
# Redirect stderr to stdout
which works for me.

Read command line arguments with input redirection operator in bash

I need to read command line arguments. First arg is script name. second one is redirection operator i.e. "<" and third one is input filename. When I tried to use "$#", I got 0. When I used "$*", it gave me nothing. I have to use "<" this operator. My input file consists of all user input data. If I don't use the operator, It asks user for the input. Can someone please help me? Thank you !
Command Line :
./script_name < input_file
Script:
echo "$*" # gave nothing
echo "$#" # gave me 0
I need to read input filename and store it to some variable. Then I have to change the extension of it. Any help/suggestions should be appreciated.
When a user runs:
./script_name <input_file
...that's exactly equivalent to if they did the following:
(exec <input_file; exec ./script_name)
...first redirecting stdin from input_file, then invoking the script named ./script_name without any arguments.
There are operating-system-specific interfaces you can use to get the filename associated with a handle (when it has one), but to use one of these would make your script only able to run on an operating system providing that interface; it's not worth it.
# very, very linux-specific, won't work for "cat foo | ./yourscript", generally evil
if filename=$(readlink /proc/self/fd/0) && [[ -e $filename ]]; then
set -- "$#" "$filename" # append filename to the end of the argument list
fi
If you want to avoid prompting for input when an argument is given, and to have the filename of that argument, then don't take it on stdin but as an argument, and do the redirection yourself within the script:
#!/bin/bash
if [[ $1 ]]; then
exec <"$1" # this redirects your stdin to come from the file
fi
# ...put other logic here...
...and have users invoke your script as:
./script_name input_file
Just as ./yourscript <filename runs yourscript with the contents of filename on its standard input, a script invoked with ./yourscript filename which invokes exec <"$1" will have the contents of filename on its stdin after executing that command.
< is used for input redirection. And whatever is at the right side of < is NOT a command line argument.
So, when you do ./script_name < input_file , there will be zero (0) command line arguments passed to the script, hence $# will be zero.
For your puprpose you need to call your script as:
./script_name input_file
And in your script you can change the extension with something like:
mv -- "$1" "${1}_new_extension"
Edit: This was not what OP wanted to do.
Altough, there is already another spot on answer, I will write this for the sake of completeness. If you have to use the '<' redirection you can do something like this in your script.
while read filename; do
mv -- "$filename" "${filename}_bak"
done
And call the script as, ./script < input_file. However, note that you will not be able to take inputs from stdin in this case.
Unfortunately, if you're hoping to take redirection operators as arguments to your script, you're not going to be able to do that without surrounding your command line arguments in quotes:
./script_name "<input_file"
The reason for this is that the shell (at least bash or zsh) processes the command before ever invoking your script. When the shell interprets your command, it reads:
[shell command (./script_name)][shell input redirection (<input_file)]
invoking your script with quotes effectively results in:
[shell command (./script_name)][script argument ("<input_file")]
Sorry this is a few years late; hopefully someone will find this useful.

How to save the command you are about to execute in bash?

Is there a better way to save a command line before it it executed?
A number of my /bin/bash scripts construct a very long command line. I generally save the command line to a text file for easier debugging and (sometimes) execution.
My code is littered with this idiom:
echo >saved.txt cd $NEW_PLACE '&&' command.py --flag $FOO $LOTS $OF $OTHER $VARIABLES
cd $NEW_PLACE && command.py --flag $FOO $LOTS $OF $OTHER $VARIABLES
Obviously updating code in two places is error-prone. Less obvious is that Certain parts need to be quoted in the first line but not the next. Thus, I can not do the update by simple copy-and-paste. If the command includes quotes, it gets even more complicated.
There has got to be a better way! Suggestions?
How about creating a helper function which logs and then executes the command? "$#" will expand to whatever command you pass in.
log() {
echo "$#" >> /tmp/cmd.log
"$#"
}
Use it by simply prepending log to any existing command. It won't handle && or || though, so you'll have to log those commands separately.
log cd $NEW_PLACE && log command.py --flag $FOO $LOTS $OF $OTHER $VARIABLES
are you looking for set -x (or bash -x)? This writes every command to standard out after executing.
use script and you will get archived everything.
use -x for tracing your script, e.g. run them as bash -x script_name args....
use set -x in your current bash (you will get echoed your commands with substitued globs and variables
combine 2 and 3 with the 1
If you just execute the command file immediately after creating it, you will only need to construct the command once, with one level of escapes.
If that would create too many discrete little command files, you could create shell procedures and then run an individual one.
(echo fun123 '()' {
echo echo something important
echo }
) > saved.txt
. saved.txt
fun123
It sounds like your goal is to keep a good log of what your script did so that you can debug it when things go bad. I would suggest using the -x parameter in your shebang like so:
#!/bin/sh -x
# the -x above makes bash print out every command before it is executed.
# you can also use the -e option to make bash exit immediately if any command
# returns a non-zero return code.
Also, see my answer on a previous question about redirecting all of this debug output to a log when --log is passed into your shell script. This will redirect all stdout and stderr. Occasionally, you'll still want to write to the terminal to give the user feedback. You can do this by saving stdout to a new file descriptor and using that with echo (or other programs):
exec 3>&1 # save stdout to fd 3
# perform log redirection as per above linked answer
# now all stdout and stderr will be redirected to the file and console.
# remove the `tee` command if you want it to go just to the file.
# now if you want to write to the original stdout (i.e. terminal)
echo "Hello World" >&3
# "Hello World" will be written to the terminal and not the logs.
I suggest you look into the xargs command. It was made to solve the problem of programtically building up argument lists and passing them off to executables for batch processing
http://en.wikipedia.org/wiki/Xargs

Resources