As we discussed earlier, the shell comes with an environment, which dictates what it can do and what not, so let's just have a look at what these variables are using the env command:
zarrelli:~$ env
...
LANG=en_GB.utf8
...
DISPLAY=:0.0
...
USER=zarrelli
...
DESKTOP_SESSION=xfce
...
PWD=/home/zarrelli/Documents
...
HOME=/home/zarrelli
...
SHELL=/bin/bash
...
LANGUAGE=en_GB:en
...
GDMSESSION=xfce
...
LOGNAME=zarrelli
...
PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games
_=/usr/bin/env
Some of the variables have been omitted for the sake of clarity; otherwise, the output would have been too long, but still we can see something interesting. We can have a look at the PATH variable content, which influences where the shell will look for a program or script to execute. We can see which shell is being currently used, by which user, what the current directory is and the previous one.
But environment variables can not only be read; they can be instanced using the export command:
zarrelli:~$ export TEST_VAR=awesome
Now, let us read it:
zarrelli:~/$ echo ${TEST_VAR}
awesome
That is it, but since this was just a test, it is better to unset the variable so that we do not leave unwanted values around the shell environment:
zarrelli:~$ unset TEST_VAR
And now, let us try to get the content of the variable:
zarrelli:~/$ echo ${TEST_VAR}
zarrelli:~/$
No way! The variable content is no more, and as you will see now, the environment variables disappear once their shell is no more. Let's have a look at the following script:
zarrelli:~$ cat setting.sh
#!/bin/bash
export MYTEST=NOWAY
env | grep MYTEST
echo ${MYTEST}
We simply instance a new variable, grep for it in the environment and then print its content to the stdout. What happens once invoked?
zarrelli@:~$ ./setting.sh ; echo ${MYTEST}
MYTEST=NOWAY
NOWAY
zarrelli:~$
We can easily see that the variable was grepped on the env output, so this means that the variable is actually instanced at the environment level and we could access its content and print it. But then we executed the echo of the content of MYTEST outside the script again, and we could just print a blank line. If you remember, when we execute a script, the shell forks a new shell and passes to it its full environment, thus the command inside the program shell can manipulate the environment. But then, once the program is terminated, the related shell is terminated, and its environment variables are lost; the child shell inherits the environment from the parent, the parent does not inherit the environment from the child.
Now, let us go back to our shell, and let us see how we can manipulate the environment to our advantage. If you remember, when the shell has to invoke a program or a script, it looks inside the content of the PATH environment variable to see if it can find it in one of the paths listed. If it is not there, the executable or the script cannot be invoked just with their names, they have to be called passing the full path to it. But have a look at what this script is capable of doing:
#!/bin/bash
echo "We are into the directory"
pwd
We print our current user directory:
echo "What is our PATH?"
echo ${PATH}
And now we print the content of the environment PATH variable:
echo "Now we expand the path for all the shell"
export PATH=${PATH}:~/tmp
This is a little tricky. Using the graphs, we preserve the content of the variable and add a, which is the delimiter for each path inside the list held by PATH, plus the ~/tmp, which literally means the tmp directory inside the home directory of the current user:
echo "And now our PATH is..."
echo ${PATH}
echo "We are looking for the setting.sh script!"
which setting.sh
echo "Found it!"
And we actually found it. Well, you could also add some evaluation to make the echo conditional, but we will see such a thing later on. Time for something funny:
echo "Time for magic!"
echo "We are looking for the setting.sh script!"
env PATH=/usr/bin which setting.sh
echo "BOOOO, nothing!"
Pay attention to the line starting with env; this command is able to overrun the PATH environment variable and to pass its own variable and related value. The same behavior can be obtained using export instead of env:
echo "Second try..."
env PATH=/usr/sbin which setting.sh
echo "No way..."
This last try is even worse. We modified the content of the $PATH variable which now points to a directory where we cannot find the script. So, not being in the $PATH, the script cannot be invoked by just its name:
zarrelli:~$ ./setenv.sh
We are in the directory:
/home/zarrelli/Documents/My books/Mastering bash/Chapter 1/Scripts
What is our PATH?
/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games
Now we expand the path for all the shell.
And now our PATH is:
/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/home
/zarrelli/tmp
We are looking for the setting.sh script!
/home/zarrelli/tmp/setting.sh
Found it!
Time for magic!
We are looking for the setting.sh script!
BOOOO, nothing!
Second try...
env: 'which': No such file or directory
No way...
Environment variable
|
Use
|
BASH_VERSION
|
The version of the current Bash session
|
HOME
|
The home directory of the current user
|
HOSTNAME
|
The name of the host
|
LANG
|
The locale used to manage the data
|
PATH
|
The search path for the shell
|
PS1
|
The prompt configuration
|
PWD
|
The path to the current directory
|
USER
|
The name of the currently logged in user
|
LOGNAME
|
Same as user
|
We can also use env with the -i argument to strip down all the environment variables and just pass to the process what we want, as we can see in the following examples. Let's start with something easy:
zarrelli:~$ cat env-test.sh
#!/bin/bash
env PATH=HELLO /usr/bin/env | grep -A1 -B1 ^PATH
Nothing too difficult, we modified the PATH variable passing a useless value because HELLO is not a searchable path, then we had to invoke env using the full path because PATH became useless. Finally, we piped everything to the input of grep, which will select all the rows (^) starting with the string PATH, printing that line and one line before and after:
zarrelli:~$ ./env-test.sh
2705-XDG_CONFIG_DIRS=/etc/xdg
2730:PATH=HELLO
2741-SESSION_MANAGER=local/moveaway:@/tmp/.ICE-unix/888,unix/moveaway:/tmp/.ICE-unix/888
Now, let's modify the script, adding -i to the first env:
zarrelli:~$ cat env-test.sh
#!/bin/bash
env -i PATH=HELLO /usr/bin/env | grep -A1 -B1 ^PATH
And now let us run it:
zarrelli:~/$ ./env-test.sh
PATH=HELLO
zarrelli:~/$
Can you guess what happened? Another change will make everything clearer:
env -i PATH=HELLO /usr/bin/env
No grep; we are able to see the complete output of the second env command:
zarrelli:~$ env -i PATH=HELLO /usr/bin/env
PATH=HELLO
zarrelli:~$
Just PATH=HELLO env with the argument -i passed to the second env process, a stripped down environment with only the variables specified on the command line:
zarrelli:~$ env -i PATH=HELLO LOGNAME=whoami/usr/bin/env
PATH=HELLO
LOGNAME=whoami/usr/bin/env
zarrelli:~$
Because we are engaged in stripping down, let us see how we can make a function disappear with the well-known unset -f command:
#!/bin/bash
echo -e "\n"
echo "The space left is ${disk_space}"
disk_space=`df -h | grep vg-root | awk '{print $4}'`
print () {
echo "The space left inside the function is ${disk_space}"
local available=yes
last=yes
echo "Is the available variable available inside the function? ${available}"
}
echo "Is the last variable available outside the function before it is invoked? ${last}"
print
echo "The space left outside is ${disk_space}"
echo "Is the available variable available outside the function? ${available}"
echo "Is the last variable available outside the function after it is invoked? ${last}"
echo "What happens if we unset a variable, like last?"
unset last
echo "Has last a referrable value ${last}"
echo "And what happens if I try to unset a while print functions using unset -f"
t
print
unset -f print
echo "Unset done, now let us invoke the function"
print
Time to verify what happens with the unset command:
zarrelli:~$ ./disk-space-function-unavailable.sh
The space left is:
Is the last variable available outside the function before it is invoked?
The space left inside the function is 202G
Is the available variable available inside the function? yes
The space left outside is 202G
Is the available variable available outside the function?
Is the last variable available outside the function after it is invoked? yes
What happens if we unset a variable, like last?
Has last a referrable value
And what happens if I try to unset a while print functions using
unset -f
The space left inside the function is 202G
Is the available variable available inside the function? yes
Unset done, now let us invoke the function:
zarrelli:~$
The print function works well, as expected before we unset it, and also the variable content becomes no longer available. Speaking about variables, we can actually unset some of them on the same row using the following:
unset -v variable1 variable2 variablen
We saw how to modify an environment variable, but what if we want to make it read-only so to protect its content from an unwanted modification?
zarrelli:~$ cat readonly.sh
#!/bin/bash
echo "What is our PATH?"
echo ${PATH}
echo "Now we make it readonly"
readonly PATH
echo "Now we expand the path for all the shell"
export PATH=${PATH}:~/tmp
Look at the line readonlyPATH, and now let's see what the execution of this script leads us to:
zarrelli:~$ ./readonly.sh
What is our PATH?
/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games
Now we make it readonly
Now we expand the path for all the shell
./readonly.sh: line 10: PATH: readonly variable
zarrelli:~$
What happened is that our script tried to modify the PATH variable that was just made readonly a few lines before and failed. This failure then led us out of the screen with a failure, and this is confirmed by printing the value of the $? variable, which holds the exit state of the last command invoked:
zarrelli:~$ echo $?
1
zarrelli:~$ echo $?
0
We will see the use of such a kind of variable later, but now what interests us is to know what that 0 and 1 mean: the first time we issued the echo command, right after invoking the script, it gave us the exit code 1, which means failure, and this makes sense because the script exited abruptly with an error. The second time we ran echo, it showed 0, which means that the last command executed, the previous echo went well, without any errors.