Expanding a shell variable in a script - bash

If I define my archive folder it in my environment and export it, how do I access it in a shell script and run a program?
ARCHIVE=/home/kschmidt/public_html/CS265/Assignments/DrMath/Archive
export ARCHIVE
./prob1
Currently, my prob1 contains this code which I get an error when I try to run.
#!/bin/bash
print ARCHIVE

You expand a shell variable by prefixing it with a $, and print it with echo or printf command - shell doesn't have a print command:
echo "$ARCHIVE"
or
printf '%s\n' "$ARCHIVE"
As an aside, it is not good to use relative paths (as in ./prob1) in a script, unless you are explicitly cding to the directory where prob1 exists. So, either:
do an explicit cd to the script directory before invoking it with a relative path
or
use an absolute path (as in /path/to/prob1)
Related:
Why is printf better than echo - on Unix & Linux Stack Exchange
Shell Parameter Expansion - GNU Bash Manual
When to wrap quotes around a shell variable?

prob1's code should be like:
#!/bin/bash
bash $ARCHIVE
then prob1 run like this:
bash prob1
I hope this help.

Related

Bash unexpected value of $0 inside script

I have two files vars.sh and main.sh with the contents:
$ cat vars.sh
#!/bin/bash
fname="$0" # should $0 equal 'vars.sh'?
$ cat main.sh
#!/bin/bash
echo $0
. vars.sh
echo $fname
When I run main.sh I get:
$ ./main.sh
./main.sh
./main.sh
My question is why is $0 inside vars.sh returning main.sh? I read man bash section about $0 but that did not help much.
Sourcing another script involves executing the sourced commands in the current shell. In the current shell, $0 refers to main.sh. You can think of sourcing as similar to "inclusion" or "copy-paste".
However, there does exist a way to get the sourced file name in bash. You can use BASH_SOURCE variable.
If you change vars.sh to:
#!/bin/bash
fname=${BASH_SOURCE[0]}
Then you'll get the sourced file's name as expected.
It is because . (source) includes commands from sourced file, in your case from vars.sh
https://ss64.com/bash/source.html
When a process is started via exec, the first first argument is usually the path to the executable (or whatever the caller decided to pass there as argument). In bash, this argument can be retrieved via $0. In your case, your process is the bash process running main.sh, so that's what is stored there. vars.sh is executed within the same process; hence, $0 is the same.

Assigning a variable in a shell script for use outside of the script

I have a shell script that sets a variable. I can access it inside the script, but I can't outside of it. Is it possible to make the variable global?
Accessing the variable before it's created returns nothing, as expected:
$ echo $mac
$
Creating the script to create the variable:
#!/bin/bash
mac=$(cat \/sys\/class\/net\/eth0\/address)
echo $mac
exit 0
Running the script gives the current mac address, as expected:
$ ./mac.sh
12:34:56:ab:cd:ef
$
Accessing the variable after its created returns nothing, NOT expected:
$ echo $mac
$
Is there a way I can access this variable at the command line and in other scripts?
A child process can't affect the parent process like that.
You have to use the . (dot) command — or, if you like C shell notations, the source command — to read the script (hence . script or source script):
. ./mac.sh
source ./mac.sh
Or you generate the assignment on standard output and use eval $(script) to set the variable:
$ cat mac.sh
#!/bin/bash
echo mac=$(cat /sys/class/net/eth0/address)
$ bash mac.sh
mac=12:34:56:ab:cd:ef
$ eval $(bash mac.sh)
$ echo $mac
12:34:56:ab:cd:ef
$
Note that if you use no slashes in specifying the script for the dot or source command, then the shell searches for the script in the directories listed in $PATH. The script does not have to be executable; readable is sufficient (and being read-only is beneficial in that you can't run the script accidentally).
It's not clear what all the backslashes in the pathname were supposed to do other than confuse; they're unnecessary.
See ssh-agent for precedent in generating a script like that.

How to run "source" command (Linux) from a perl script?

I am trying to source a script from a Perl script (script.pl).
system ("source /some/generic/script");
Please note that this generic script could be a shell, python or any other script. Also, I cannot replicate the logic present inside this generic script into my Perl script. I tried replacing system with ``, exec, and qx//. Each time I got the following error:
Can't exec "source": No such file or directory at script.pl line 18.
I came across many forums on the internet, which discussed various reasons for this problem. But none of them provided a solution. Is there any way to run/execute source command from a Perl script?
In bash, etc, source is a builtin that means read this file, and interpret it locally (a little like a #include).
In this context that makes no sense - you either need to remove source from the command and have a shebang (#!) line at the start of the shell script that tells the system which shell to use to execute that script, or you need to explicitly tell system which shell to use, e.g.
system "/bin/sh", "/some/generic/script";
[with no comment about whether it's actually appropriate to use system in this case].
There are a few things going on here. First, a child process can't change the environment of its parent. That source would only last as long as its process is around.
Here's a short program that set and export an environment variable.
#!/bin/sh
echo "PID" $$
export HERE_I_AM="JH";
Running the file does not export the variable. The file runs in its own proces. The process IDs ($$) are different in set_stuff.sh and the shell:
$ chmod 755 set_stuff.sh
$ ./set_stuff.sh
PID 92799
$ echo $$
92077
$ echo $HERE_I_AM # empty
source is different. It reads the file and evaluates it in the shell. The process IDs are the same in set_stuff.sh and the shell, so the file is actually affecting its own process:
$ unset HERE_I_AM # start over
$ source set_stuff.sh
PID 92077
$ echo $$
92077
$ echo $HERE_I_AM
JH
Now on to Perl. Calling system creates a child process (there's an exec in there somewhere) so that's not going to affect the Perl process.
$ perl -lwe 'system( "source set_stuff.sh; echo \$HERE_I_AM" );
print "From Perl ($$): $ENV{HERE_I_AM}"'
PID 92989
JH
Use of uninitialized value in concatenation (.) or string at -e line 1.
From Perl (92988):
Curiously, this works even though your version doesn't. I think the different is that in this there are no special shell metacharacters here, so it tries to exec the program directory, skipping the shell it just used for my more complicated string:
$ perl -lwe 'system( "source set_stuff.sh" ); print $ENV{HERE_I_AM}'
Can't exec "source": No such file or directory at -e line 1.
Use of uninitialized value in print at -e line 1.
But, you don't want a single string in that case. The list form is more secure, but source isn't a file that anything can execute:
$ which source # nothing
$ perl -lwe 'system( "source", "set_stuff.sh" ); print "From Perl ($$): $ENV{HERE_I_AM}"'
Can't exec "source": No such file or directory at -e line 1.
Use of uninitialized value in concatenation (.) or string at -e line 1.
From Perl (93766):
That is, you can call source, but as something that invokes the shell.
Back to your problem. There are various ways to tackle this, but we need to get the output of the program. Instead of system, use backticks. That's a double-quoted context so I need to protect some literal $s that I want to pass as part of the shell commans
$ perl -lwe 'my $o = `echo \$\$ && source set_stuff.sh && echo \$HERE_I_AM`; print "$o\nFrom Perl ($$): $ENV{HERE_I_AM}"'
Use of uninitialized value in concatenation (.) or string at -e line 1.
93919
From Shell PID 93919
JH
From Perl (93918):
Inside the backticks, you get what you like. The shell program can see the variable. Once back in Perl, it can't. But, I have the output now. Let's get more fancy. Get rid of the PID stuff because I don't need to see that now:
#!/bin/sh
export HERE_I_AM="JH";
And the shell command creates some output that has the name and value:
$ perl -lwe 'my $o = `source set_stuff.sh && echo HERE_I_AM=\$HERE_I_AM`; print $o'
HERE_I_AM=JH
I can parse that output and set variables in Perl. Now Perl has imported part of the environment of the shell program:
$ perl -lwe 'my $o = `source set_stuff.sh && echo HERE_I_AM=\$HERE_I_AM`; for(split/\R/,$o){ my($k,$v)=split/=/; $ENV{$k}=$v }; print "From Perl: $ENV{HERE_I_AM}"'
From Perl: JH
Let's get the entire environment, though. env outputs every value in the way I just processed it:
$ perl -lwe 'my $o = `source set_stuff.sh && env | sort`; print $o'
...
DISPLAY=:0
EC2_PATH=/usr/local/ec2/ec2-api-tools
EDITOR=/usr/bin/vi
...
I have a few hundred varaibles set in the shell, and I don't want to expose most of them. Those are all set by the Perl process, so I can temporarily clear out %ENV:
$ perl -lwe 'local %ENV=(); my $o = `source set_stuff.sh && env | sort`; print $o'
HERE_I_AM=JH
PWD=/Users/brian/Desktop/test
SHLVL=1
_=/usr/bin/env
Put that together with the post processing code and you have a way to pass that information back up to the parent.
This is, by the way, similar to how you'd pass variables back up to a parent shell process. Since that output is already something the shell understands, you use the shell's eval instead of parsing it.
You can't. source is a shell function that 'imports' the contents of that script into your current environment. It's not an executable.
You can replicate some of it's functionality by rolling your own - run or parse whatever you're 'sourcing' and capture the result:
print `. file_to_source; echo $somevar`;
or similar.

Store command to file

I'd like to create a script A which creates a script B. Script B creates a directory. So I created a file with this content, grant x permission to it, then execute it. Unfortunately it doesn't run as I expect. It makes directory first then create an empty file. Why?
#!/bin/bash
batch=`mkdir /home/hieund/bpl`
echo $batch > newfile
Update:
After trying your solution, I have:
#!/bin/bash
$myPath=$HOME/bpl
batch='mkdir ' $myPath
echo $batch > newfile
It doesn't work as well. Same unexpected behavior.
Update:
#!/bin/bash
$myPath=$HOME/bpl
batch="mkdir $myPath"
echo $batch > newfile
It doesn't work too. Same unexpected behavior.
bash: /home/hieund/bpl=/home/hieund/bpl: No such file or directory
It makes a directory at the moment of assignment, because you said this with "command substitution"
batch=`mkdir /home/hieund/bpl`
The flow of execution
mkdir /home/... - creates the directory - because of backticks - command substitution
the mkdir returns nothing, therefore
the assignment is like batch= (it assigns nothing)
the echo $batch echoes the "nothing" so: echo > newfile
you should to use
batch='mkdir /home/hieund/bpl'
for embedding variable use double quotes
batch="mkdir $myPath"
You always can use the bash -x script - to show what is executing. E.g. having a script myscript.sh
#!/bin/bash
MYDIR="./somedir"
batch="mkdir $MYDIR"
echo "$batch" > newfile
the command
bash -x myscript.sh
will show the execution of command and arguments. (note, not shown redirections)
+ MYDIR=./somedir
+ batch='mkdir ./somedir'
+ echo 'mkdir ./somedir'
One comment: You should generally assign things to variables with double quotes, because you can avoid problems with spaces. Note
myvar=$VAR/some
and
myvar="$VAR/some"
makes a big difference when the $VAR contains spaces.
Because backticks are not used for strings.
batch="mkdir /home/hieund/bpl"

Can you wrapper each command in GNU's make?

I want to inject a transparent wrappering command on each shell command in a make file. Something like the time shell command. ( However, not the time command. This is a completely different command.)
Is there a way to specify some sort of wrapper or decorator for each shell command that gmake will issue?
Kind of. You can tell make to use a different shell.
SHELL = myshell
where myshell is a wrapper like
#!/bin/sh
time /bin/sh "$0" "$#"
However, the usual way to do that is to prefix a variable to all command calls. While I can't see any show-stopper for the SHELL approach, the prefix approach has the advantage that it's more flexible (you can specify different prefixes for different commands, and override prefix values on the command line), and could be visibly faster.
# Set Q=# to not display command names
TIME = time
foo:
$(Q)$(TIME) foo_compiler
And here's a complete, working example of a shell wrapper:
#!/bin/bash
RESULTZ=/home/rbroger1/repos/knl/results
if [ "$1" == "-c" ] ; then
shift
fi
strace -f -o `mktemp $RESULTZ/result_XXXXXXX` -e trace=open,stat64,execve,exit_group,chdir /bin/sh -c "$#" | awk '{if (match("Process PID=\d+ runs in (64|32) bit",$0) == 0) {print $0}}'
# EOF
I don't think there is a way to do what you want within GNUMake itself.
I have done things like modify the PATH env variable in the Makefile so a directory with my script linked to all name the bins I wanted wrapped was executed rather than the actual bin. The script would then look at how it was called and exec the actual bin with the wrapped command.
ie. exec time "$0" "$#"
These days I usually just update the targets in the Makefile itself. Keeping all your modifications to one file is usually better IMO than managing a directory of links.
Update
I defer to Gilles answer. It's a better answer than mine.
The program that GNU make(1) uses to run commands is specified by the SHELL make variable. It will run each command as
$SHELL -c <command>
You cannot get make to not put the -c in, since that is required for most shells. -c is passed as the first argument ($1) and <command> is passed as a single argument string as the second argument ($2).
You can write your own shell wrapper that prepends the command that you want, taking into account the -c:
#!/bin/sh
eval time "$2"
That will cause time to be run in front of each command. You need eval since $2 will often not be a single command and can contain all sorts of shell metacharacters that need to be expanded or processed.

Resources