1

Many shell scripts, including the bash profile, are simply lists of environment variable settings. One such script on Debian is /etc/os-release which looks like this:

PRETTY_NAME="Debian GNU/Linux 10 (buster)"
NAME="Debian GNU/Linux"
VERSION_ID="10"
VERSION="10 (buster)"
VERSION_CODENAME=buster
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"

The VERSION_CODENAME is particularly useful for adding to apt sources (/etc/apt/sources.list) for, say, Steam to work on ChromeOS. Note that most instructions hard-code this value which can cause compatibility problems.

So my question then is how to echo an env var such as VERSION_CODENAME from a file such as /etc/os-release without using source? That's key because I don't want to clutter up my environment variables with these for a one-time use.

Here's what I know I can do now but it leaves the variables in my current environment which is undesirable:

source /etc/os-release && echo "deb http://httpredir.debian.org/debian/ $VERSION_CODENAME main contrib non-free | sudo tee -a /etc/apt/sources.list"

I thought perhaps there is a way to start a new (temporary) bash process and load the variables into that environment. I haven't been able to figure that out without an actual shell script.

Neil C. Obremski
  • 18,696
  • 24
  • 83
  • 112
  • 1
    The use of quotes here is a critical detail: It means your values are _in shell syntax_, so they're expected to actually be executed by a shell (as `source` does). – Charles Duffy Mar 25 '21 at 18:10
  • 3
    Have you considered `(. /etc/os-release && ...)`, so the variables go away when you hit the `)`? – Charles Duffy Mar 25 '21 at 18:10
  • https://stackoverflow.com/questions/38964946/reading-key-value-parameters-from-a-file-into-a-shell-script covers this except for the need to honor shell-like quoting. We have another duplicate that does cover that, I'm just looking for it. – Charles Duffy Mar 25 '21 at 18:11
  • BTW, `source /etc/os-release` _doesn't_ create environment variables by default; it creates _shell_ variables. `export` is needed to convert shell variables into environment variables. – Charles Duffy Mar 25 '21 at 19:38
  • Thanks @CharlesDuffy for all the good info. I'm self-taught-via-mostly-experimentation on bash and so these distinctions in terminology are really helpful – Neil C. Obremski Mar 29 '21 at 03:21

4 Answers4

2

4 different answer here...

Sorry, but there is more than one way;). You may found a lot of other ways, but there are the most appropriate (quickness, efficience, footprint, readability...).

1. import through sed to populate associative arrray:

declare -A IMPORTED="($(sed < /etc/os-release 's/^\([^=]\+\)=/[\1]=/'))"

Then

echo ${IMPORTED[VERSION_CODENAME]}
buster

2. extract required field (by using sed again)

AltVersionCodename=$(sed </etc/os-release -ne 's/^VERSION_CODENAME=//p')
echo $AltVersionCodename
buster

3. parenthesis to drop down to subshell using his own environment

( . /etc/os-release ; echo $VERSION_CODENAME )
buster

echo $VERSION_CODENAME
 

Current environment don't know about $VERSION_CODENAME

4. reading variable file in pure bash, without forks

As we are working on a small file, we could use loop to read the file until required info is found:

while IFS== read varname value;do
    [ "$varname" = "VERSION_CODENAME" ] &&
        ImportedVersionCodename=$value && break
done </etc/os-release
echo $ImportedVersionCodename 
buster
F. Hauri - Give Up GitHub
  • 64,122
  • 17
  • 116
  • 137
  • Is it well-documented that this is fully-supported when the `[key]=value` strings are all (including the `[`, `]` and `=`; obvs., `key` and `value` are data) data instead of code? I'd consider it a security bug, myself, and something to fix at the parser level (breaking this code as a side effect) in future releases of the shell. Letting values substituted into a `declare` line evaluate arbitrary code is pretty well-recognized at this point as a security-impacting design misfeature; personally, I'd be hesitant to rely on it. – Charles Duffy Mar 25 '21 at 18:20
  • @CharlesDuffy Yes, but using `source` or `eval` could lead to security issues too! – F. Hauri - Give Up GitHub Mar 27 '21 at 08:01
  • Surely. I'm not so much grousing about the security here (if someone can write to root-owned files in `/etc` there's no defense), but about futureproofing. – Charles Duffy Mar 27 '21 at 17:09
1

I thought perhaps there is a way to start a new (temporary) bash process and load the variables into that environment.

That's what using parenthesis to create a subshell does.

(
  . /etc/os-release
  echo "deb http://httpredir.debian.org/debian/ $VERSION_CODENAME main contrib non-free" \
  | sudo tee -a /etc/apt/sources.list
)

...as soon as the ) is hit, your variables are removed, as the subshell they were loaded into exits.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • 1
    When `/etc/os-release` has a line `touch /tmp/friendly`, you might prefer a solution without sourcing the code. – Walter A Mar 25 '21 at 19:10
  • If someone can change root-owned files in `/etc`, they can already run any code they want. – Charles Duffy Mar 25 '21 at 19:42
  • OP writes `Many shell scripts, including the bash profile, are simply lists of environment variable settings. One such script on Debian is /etc/os-release`. He might want to use the solution for parsing some userfile with environment settings. – Walter A Mar 25 '21 at 23:11
1

Or using read and printf like this:

while read line; do
    name=${line%%=*}
    data=${line#*=}
    printf -v $name "${data//\"}"
done < vars
Ivan
  • 6,188
  • 1
  • 16
  • 23
  • Though this drops _all_ quotes, not just beginning and ending ones. The file could contain something like `PRETTY_NAME="Hello \"world\""`, moreover, and we'd be losing the literal quotes in addition to the syntactic ones. – Charles Duffy Mar 25 '21 at 23:54
1

The next function only sources the line with the key (value in single quotes).

my_set() {
   configfile="$1"
   key="$2"
   print -v "$key" $(sed -n "s/^${key}=//p" "${configfile}")
}
my_set /etc/os-release VERSION_CODENAME
echo "deb http://httpredir.debian.org/debian/ $VERSION_CODENAME main.."

When you don't need the var in the environment, use

my_set2() {
   configfile="$1"
   key="$2"
   sed -n "s/^${key}=//p" "${configfile}"
}

echo "deb http://httpredir.debian.org/debian/ $(my_set2 /etc/os-release VERSION_CODENAME) main.."
Walter A
  • 19,067
  • 2
  • 23
  • 43
  • Why the `source`? You could use a safer indirect evaluation mechanism -- `printf -v "$key" %s "$(sed ...)"`, f/e. Not that _either_ of these approaches actually add variables to the environment unless `set -a` is enabled. – Charles Duffy Mar 25 '21 at 19:36
  • @CharlesDuffy `printf` is much better than `source` (changed, thanks). I tested `set -a` and `set +a`, but my variable is added in the environment in both cases. The function is performed in the current shell. The setting `set +a` works different for a variable in the main code and referred to in a subshell. – Walter A Mar 25 '21 at 23:06
  • ...when did subshells come into play? I don't see any subshells here (except the transient one in the command substitution that runs `sed`, but no variable assignments take place there) , so I'm not sure what the relevance is. – Charles Duffy Mar 25 '21 at 23:52
  • I misunderstood the reason for `set -a`/`set +a`, which are needed for subshells. With `my_set()` new variables like VERSION_CODENAME can be used in the current shell. When you said they are not added to the environment, you thought of the environment of future subshells. I could use `export "${key}"` but I was only interested in the variables of the current shell. – Walter A Mar 26 '21 at 09:43
  • A subshell is created by `fork()`ing the parent shell with no `exec`, so all variables survive, not just exported ones; thus, `set -a` doesn't have any impact on whether variables are seen in subshells. No, I didn't mean "the environment of future subshells". `foo=bar` doesn't set an environment variable _at all_, not even in the current shell. It's a _regular shell variable_, not an environment variable. Shell variables are only _environment variables_ if `setenv()` is called to copy them to the environment (which as a side effect makes them survive across a `fork()` boundary). – Charles Duffy Mar 26 '21 at 14:41
  • ...which is to say, "exported variable" and "environment variable" are synonymous. If it isn't exported to the environment, it isn't an environment variable. – Charles Duffy Mar 26 '21 at 14:44
  • 1
    (See section 2.5.3 of the POSIX sh standard talking about variable assignments; it describes how variables are "marked for export immediately" only when those variables were _imported_ from the environment; in other contexts, it refers to variables created by a regular assignment, a `read` command, etc. only as "variables" or "shell variables") – Charles Duffy Mar 26 '21 at 15:22