not able to use local variable outside the function after exporting - linux

This is my script , even after using the export command not able to use variable outside of the block. Below is the code that i have tried. I also tried other option like declare -x var, but that is also not working.
Can someone please please comment on this , am i doing right ?
#!/bin/bash
{
var="123"
export var # exporting the variable so that i can access from anywhere
echo "var is "$var # able to get the value of this variable
} | tee log.txt
echo "var is "$var # not able to get the value of this variable

Because the pipe is causing the code between the braces to execute in a sub-shell you need to find a way to capture that data as opposed to storing it in a variable that is not accessible from the rest of the code. An example would be to store the output of a function in a variable, or to access it via command substitution. If you have script.sh as such:
#!/bin/bash
function get_pizza() {
echo "Pizza"
}
myvar=$(get_pizza)
printf "myvar is '%s'\n" $myvar
echo "Plain echo follows:"
echo $(get_pizza)
and then run bash script.sh you will get output as such:
[user#host]$ bash ./script.sh
myvar is 'Pizza'
Plain echo follows:
Pizza
Then if you still want to write to a file via tee, you can pipe your whole script to tee:
bash ./script.sh | tee foo.log
If you only want parts of the script to goto a file, you'll can also handle that with I/O redirection within the script: echo pizza > foo.log

Related

Exporting environment variables both to bash as csh using a bash script with functions

I have a bash shell-script with a function which exports an environment variable.
For sake of argument lets use the following example:
#!/bin/bash
function my_function()
{
export my_env_var=$1
}
Since the whole purpose is to export the variable to the main shell I source it.
When the main shell is bash this works fine:
<bash-shell>
> source ~/tmp/my_test.sh
> my_function test
> echo $my_env_var
test
But other customers use csh and there things start to fail if I use the same command with the same script, since csh does not know functions :-(
<csh-shell>
% source ~/tmp/my_test.sh
Badly placed ()'s
I already tried to wrap it in a wrapper-script:
#!/bin/sh
bash -c 'source ~/tmp/my_test.sh; my_function test`
echo my_env_var = $my_env_var
But my_env_var is not exported in this way:
<csh-shell>
% source ~/tmp/my_test2.sh
my_env_var: Undefined variable.
Where it is known in the bash shell (as can be seen by changing the 2nd script to:
#!/bin/sh
bash -c 'source ~/tmp/my_test.sh; my_function test; echo my_env_var in bash = $my_env_var`
echo my_env_var = $my_env_var
<csh-shell>
% source ~/tmp/my_test2.sh
my_env_var in bash = test
my_env_var: Undefined variable.
What am I missing / doing wrong so the script exports the variable when it is called from bash and when it is called from csh?
The Bourne shell and csh are not compatible; many commands are different, and csh misses many features (it doesn't have functions at all). Plus, sooner or later you're going to have someone who uses fish, which is different yet still. The only way to make a non-trivial script work for both is to write it twice.
That said, if you want to set some environment variables then the general strategy is to create a script which outputs the required commands; this can be in any language (shell, Python, C); for example:
#!/bin/sh
# ... do work here ...
var="foo"
# Getting the shell in a cross-platform way isn't too easy. This was only tested
# on Linux. Can add a "-c" or "-f" flag if you need cross-platform support.
shell=$(ps -ho comm $(ps -ho ppid $$))
case "$shell" in
(csh|tcsh) echo "setenv VAR $var" ;;
(fish) echo "set -Ux VAR $var" ;;
(*) echo "export VAR=$var"
esac
And when you run it, it outputs the appropriate commands:
% ./work
export VAR=foo
% tcsh
> ./work
setenv VAR foo
> fish
martin#x270 ~> ./work
set -Ux VAR foo
And to actually set it, eval the output like so:
% eval $(./work)
% echo $VAR
foo
% tcsh
> eval `./work`
> echo $VAR
foo
> fish
martin#x270 ~> eval (./work)
martin#x270 ~> echo $VAR
foo
The downside of this is that informational messages, warnings, etc. will also get eval'd; to solve this make sure to always output these to stderr:
echo >&2 "warning: foo"
If you don't want to run eval you can also use something slightly more complicated which prints VAR=foo and then create a Bourne and csh wrapper script to parse those lines, but "output the variables you want to set, instead of directly setting them" is the general approach to take to make something work in multiple incompatible shells.

How to store elapsed time of a variable assignment to another variable in bash script?

I wanted to run a maven command and store the console output to a variable and in turn, store the real time of the mentioned operation in another variable. I wrote the following command -
x1=`( time t1=$( mvn test -Drat.skip)) 2>&1 | grep real`
When I echo variable x1 I get 0m17.430s which is the desired output but when I echo variable t1 it prints nothing! How can I store the console output of mvn test -Drat.skip in t1?
Everything inside of () or backticks happens in a subshell. Variable values aren't exported from a subshell back to the parent shell.
You can assign both the output of the command and output of time into a variable and then extract it from there:
#!/bin/bash
all=$((time mvn test -Drat.skip )2>&1)
time=$(tail -n3 <<< "$all" | grep real)
output=$(head -n-3 <<< "$all")
As #choroba said t1 is created in different subshell and can't be exported back.
You cat test it like this:
t1=test
x1=`(time t1=$(echo ok); echo $t1) 2>&1`
echo $t1
echo $x1
The output will be:
$ echo $t1
test
$ echo $x1
real 0m0,001s user 0m0,001s sys 0m0,001s ok
But this litle hack may help
fun () { t1=$(mvn test -Drat.skip); }
x1=$((time fun) 2>&1 | grep real)

BASH save stdout to new file upon execution

please bear with me if my terminology or syntax is less than stellar (still learning). I currently have a simple bash script that checks the arguments of the command and outputs files names with matching text. This part of my script works correctly via a grep command and piped to xargs for proper formatting.
When running the script, I run through a simple loop to check if the value is null and then move to running my variable/search if not.
My question is: Is it possible to have this script output via stdout AND also save a new file each time it is run with the user input and date/time? (but not overwrite) EX: report-bob-0729161500.rpt
I saw same other suggestions to use tee with the command, but I was trying to get it to work within the script. Similarly, another suggestion stated to utilize exec > >(tee -i logfile.txt), but I am unsure how to properly format this to include the date/time and $1 input into new files each time the script is executed.
Any help or suggested resources?
Thank you.
SEARCH=`[search_variable]`
if [ -z "$SEARCH" ]
then
echo "$1 not found."
else
echo -e "REPORT LISTING\n\n"
echo "$SEARCH"
fi
EDIT: I did try simply piping the echo statements to the tee command, which does work. However, I am still curious if anyone has other suggestions to accomplish this same task via alternative methods. Thank you.
With echo statements piped to tee:
SEARCH=`[search_variable]`
DATE=`date +"%m%d%y%k%M"`
if [ -z "$SEARCH" ]
then
echo "$1 not found."
else
echo -e "REPORT LISTING\n\n" | tee tps-list-$1-$DATE.rpt
echo "$SEARCH" | tee tps-list-$1-$DATE.rpt
fi
If you want to do it within the script, why then not just write to
both standard output and the file (using append where appropriate?).
Maybe a bit more writing, but it gives complete control.
Leon

read more than one parameters right after the command in bash

I am making a bash script and I want it to be just one line, meaning it will not have any interaction with the users and the parameters will be on the same line as the command. Once the user clicks return, it will output the result.
Right now, I have something that looks like this:
#! \bin\bash
read $1 $2
do something with $1 and $2
However, if I name my script "test" when I type in test at the beginning of the command line, I will have to type enter for the rest of the script to be executed. How should I modify it so that I can run the entire thing on just one line?
The standard way to pass parameters to a script is not with read (which actively waits for input from stdin), but just to call your script with the parameters on the same line:
./my_script.sh param1 param2
Then inside the script, you can access these parameters using $1, $2, etc. Example (note also the first line - this describes what shell should be used to run the script, and should be a valid path - ie /bin/bash, not backslashes):
#!/bin/bash
echo "First: $1 Second: $2"
Then call the script:
$ ./my_script.sh Hello There
First: Hello Second: There
What you probably need is this :
You script name test.sh contains the following:
#!/bin/bash
echo "$1 $2"
Then, change permission so that you can execute the script on the command line :
chmod u+x test.sh
and run the script with arguments (two in this case) :
./test.sh tik tak
will return
tik tak

Shell script that writes a shell script

Two questions: how can I write a shell variable from this script into its child script?
Are there any easier ways to do this?
If you can't follow what I'm doing, I'm:
1) starting with a list of directories whose names will be stored as values taken by $i
2) cd'ing to every value of $i and ls'ing its contents
3) echoing its contents into a new script with the name of the directory via cat
4) using echo and cat to write a new script that contains the ls'd values of $i and sends them all to a blogging email address called $i#tumblr.com
#/bin/sh
read -d '' commands <<EOF
#list of directories goes here
dir1
dir2
dir3
etc...
EOF
for i in $commands
do
cd $SPECIALPATH/$i
echo ("#/bin/sh \n read -d '' directives <<EOF \n") | cat >> $i.sh
ls | cat >> $i.sh
echo ("EOF \n for q in $directives \n do \n uuencode $q $q | sendmail $i \n done \n") | cat >> $i.sh
# NB -- I am asking the script to write the shell variable $i into the new
# script, called $i.sh, as the email address specified, in the middle of an
# echo statement... I am well aware that it doesn't work as is
chmod +x $i.sh
./$i.sh
done
You are abusing felines a lot - you should simply redirect, rather than pipe to cat which appends.
You can avoid the intermediary $i.sh file by bundling all the output that goes to the file with a single I/O redirection that pipes direct into a shell - no need for the intermediate file to clean up (you didn't show that happening) or the chmod operation.
I would have done this using braces:
{
echo "..."
ls
echo "..."
} | sh
However, when I looked at the script in that form, I realized that wasn't necessary. I've left the initial part of your script unchanged, but the loop is vastly simpler like this:
#/bin/sh
read -d '' commands <<EOF
#list of directories goes here
dir1
dir2
dir3
etc...
EOF
for i in $commands
do
(
cd $SPECIALPATH/$i
ls |
while read q
do uuencode $q $q | sendmail $i
done
)
done
I'm assuming the sendmail command works - it isn't the way I'd try sending email. I'd probably use mailx or something similar, and I'd avoid using uuencode too (I'd use a base-64 encoding, left to my own devices):
do uuencode $q $q | mailx -s "File $q" $i#tumblr.com
The script also uses parentheses around the cd command. It means that the cd command and what follows is run in a sub-shell, so the parent script does not change directory. In this case, with an absolute pathname for $SPECIALDIR, it would not matter much. But as a general rule, it often makes life easier if you isolate directory changes like that.
I'd probably simplify it still further for general reuse (though I'd need to add something to ensure that SPECIALPATH is set appropriately):
#/bin/sh
for i in "$#"
do
(
cd $SPECIALPATH/$i
ls |
while read q
do uuencode $q $q | sendmail $i
done
)
done
I can then invoke it with:
script-name $(<list-of-dirs)
That means that without editing the script, it can be reused for any list of directories.
Intermediate step 1:
for i in $commands
do
(
cd $SPECIALPATH/$i
{
echo "read -d '' directives <<EOF"
ls
echo "EOF"
echo "for q in $directives"
echo "do"
echo " uuencode $q $q | sendmail $i"
echo "done"
} |
sh
)
done
Personally, I find it easier to read the generated script if the code that generates makes the generated script clear - using multiple echo commands. This includes indenting the code.
Intermediate Step 2:
for i in $commands
do
(
cd $SPECIALPATH/$i
{
ls |
echo "while read q"
echo "do"
echo " uuencode $q $q | sendmail $i"
echo "done"
} |
sh
)
done
I don't need to read the data into a variable in order to step through each item in the list once - simply read each line in turn. The while read mechanism is often useful for splitting up a line into multiple variables too: while read var1 var2 var3 junk will read the first field into $var1, the second into $var2, the third into $var3, and if there's anything left over, it goes into $junk. If you've generated the data accurately, there won't be any junk; but sometimes you have to deal with other people's data.
If the generated script is meant to be temporary, I would not use files. Besides, chmoding them to executable sounds unsafe. When I needed to parallel my scripting, I used a bash script to form a set of commands (in an array, split the array in two, then implode the array) to a single \n-separated string and then pass that to a new bash instance.
Basically, in bash:
for orig in "$#"
do
commands="$commands echo \"echoeing stuff here for arguments $orig\" \n"
done
echo -e $commands |bash
And a small tip: if the script doesn't need supervising, throw in a & after the piped bash to make your first script quit and do the rest of the work forked background.
If you export a variable
export VAR1=FOO
it'll be present in any child processes.
If you take a look at the init scripts, /etc/init..d/* you'll notice that many source another file full of "external" definitions. You could set up a file like that and have your child script source these files.

Resources