export, read, and the child environment
◈ 9 cardsExplain what export does and, more importantly, what it does not do; protect and remove variables with readonly and unset; and read a line of input into a variable list, including the surplus-word rule.
Export hands out a copy
The cleanest way to learn what export does is to watch a value fail to come back. Three tiny scripts, run as a cascade.
outer sets a variable, exports it, calls a second script, and then looks at the variable again:
X=1
export X
echo "outer before: $X"
sh inner
echo "outer after: $X"
inner reports what it received, changes it, reports the change, and calls a third script:
echo "inner sees: $X"
X=2
echo "inner set: $X"
sh grandchild
grandchild does nothing but report:
echo "grandchild sees: $X"
Running sh outer prints:
outer before: 1
inner sees: 1
inner set: 2
grandchild sees: 2
outer after: 1
Read the first and last lines together, because that pair is the whole lesson. The child received 1, deliberately set it to 2, and the parent still has 1 when the child is gone. A child cannot change a variable in its parent. There is no mechanism for it, not a flag, not a syntax — the values simply travel in one direction.
What export actually does is mark a name so that its current value is copied into the environment handed to every process the shell creates afterwards. fork gives the child a copy of that environment; the child's writes go into its copy. The parent's copy is untouched because it was never shared in the first place.
The grandchild line shows the second half of the rule: the export attribute is inherited along with the value. inner never wrote export X itself, yet the grandchild still sees 2, because X arrived in inner already marked for export and stayed marked. Drop the export from outer and the whole cascade changes — inner sees the null string, and so does the grandchild, because nothing was ever put in the environment for either of them to inherit.
The practical corollary shows up as soon as you write a script that is supposed to change your environment. It cannot, if you run it: it runs in a child. That is why configuration scripts are sourced with the dot command — . ./setpath — which makes the current shell read the file itself instead of forking a child to read it.
Removing and protecting names
unset name removes a variable entirely; afterwards it expands to the null string like any name that was never set. unset takes a space-separated list.
readonly name marks a variable immutable. Any later assignment is refused, and — this is the part people are caught by — so is unset. A read-only variable cannot be released for the rest of that shell's life; the only way out is to exit the shell. With no arguments, readonly lists the names currently marked. The natural use is a constant near the top of a script that must not be clobbered by a later edit:
readonly MAILDOMAIN=comp325.athabascau.ca
Reading input into variables
read takes one line from standard input, splits it into words on the characters in IFS (space, tab and newline by default), and assigns the words to the names you list. Two rules govern the mismatch cases, and both are examinable.
Surplus words all land in the last variable. Given the line alpha beta gamma delta and the command read a b c, you get a=alpha, b=beta, and c=gamma delta — c keeps everything that was left, spaces and all. That is deliberate and useful: read user rest splits a line into its first field and its remainder in one step.
Too few words leaves the rest null. The same read a b c on the line one two sets a=one, b=two and c to the null string, with no error and no non-zero status.
read returns a non-zero status when it hits end of file with nothing to read, and that is the whole engine behind the standard file-reading loop you will write in Lesson 6.
To prompt, print without a newline first. The textbook uses echo -n, which works on Linux; printf is the portable spelling and behaves the same everywhere:
printf "Directory name: "
read dirname
And quote the result when you use it. read will happily put a two-word answer into dirname, which is exactly the value that breaks an unquoted [ -d $dirname ] two lessons from now.