20

When I cat /etc/os-release I get the following:

PRETTY_NAME="Kali GNU/Linux Rolling"
NAME="Kali GNU/Linux"
ID=kali
VERSION="2018.1"
VERSION_ID="2018.1"
ID_LIKE=debian
ANSI_COLOR="1;31"
HOME_URL="http://www.kali.org/"
SUPPORT_URL="http://forums.kali.org/"
BUG_REPORT_URL="http://bugs.kali.org/"

How would I grab kali from ID= in bash? How would I grab 2018.1 from VERSION= in bash?

1
  • Why did you think they would work?
    – jesse_b
    Commented Mar 22, 2018 at 12:01

5 Answers 5

28

Sourcing and awk are poor ideas

Sourcing these sorts of files with a shell script interpreter is a poor idea, as it introduces yet another place where the malicious can hide shell script to be executed with superuser permissions, or can introduce things like an override for variables like PATH, LANG, and LD_LIBRARY_PATH.

% cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 9 (stretch with subversive \$PATH)"
NAME="Debian GNU/Linux"
VERSION_ID="9"
VERSION="9 (stretch)"
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
echo 1>&2 'install -m 0644 /etc/shadow /home/malefactor/etc/ 2>/dev/null'
PATH="/home/malefactor/bin:${PATH}"
%
% ( source /etc/os-release ; command -v start-stop-daemon ; )
install -m 0644 /etc/shadow /home/malefactor/etc/ 2>/dev/null
/home/malefactor/bin/start-stop-daemon
%

Sourcing actually violates one of the defined semantics for /etc/os-release, which is that (to quote its manual) "variable expansion is explicitly not supported".

Similarly, awk as supplied in Archemar's answer does not correctly handle quoting; and any value can be quoted. Notice how Archemar's answer does not address the VERSION value in the question, which the given awk script does not provide the correct value for, because it erroneously retains the quoting and does not handle escape sequences.

% awk -F= '$1=="VERSION" { print $2 ;}' /etc/os-release
"9 (stretch)"      
% awk -F= '$1=="PRETTY_NAME" { print $2 ;}' /etc/os-release
"Debian GNU/Linux 9 (stretch with subversive \$PATH)"
%

/etc/os-release is actually permitted a fairly broad latitude in quoting, and also requires that escape sequences be processed properly. awk is not really the right tool for the job at all, here.

There are other, subtler, problems such as leakage of already set variables into the results if the actual key sought is missing from the file.

% ( source /etc/os-release ; echo $LANG ; )
install -m 0644 /etc/shadow /home/malefactor/etc/ 2>/dev/null
en_GB.UTF-8
%

There's also the fact that if ID or PRETTY_NAME are missing, they are defined as having default values. And on top of that, there's the fallback to /usr/lib/os-release to deal with.

Learning from other people

Files that contain key-equals-value assignments, with sh-style quoting, escaping, and commentary, are a fairly common thing. Some programming languages have library functions for dealing with them directly. The lesson of OpenBSD's rules shift on /etc/rc.conf in OpenBSD 5.6 is that it is the wiser course to use such library functions, if available, or dedicated tools that have nowhere near the capabilities of a full shell interpreter, for processing such files.

From shell scripts, I for one use a tool named read-conf to process such files:

% read_os() {
    if test -r /etc/os-release
    then
        clearenv setenv "$1" "$2" read-conf /etc/os-release printenv "$1"
    else
        clearenv setenv "$1" "$2" read-conf /usr/lib/os-release printenv "$1"
    fi
}
% read_os ID linux
debian
% read_os VERSION
9 (stretch)
% read_os PRETTY_NAME Linux
Debian GNU/Linux 9 (stretch with subversive $PATH)
% read_os PATH
/home/malefactor/bin:${PATH}
% read_os LANG
%

The aforegiven relies on setenv, read-conf, and printenv commands being built-in commands in the nosh toolset, so that clearenv, setenv, and read-conf each find the next command in the chain without using the PATH environment variable at all. (Before I added the built-in printenv, the slightly longer original one-liners made careful use of "`command -v printenv`" to avoid searching for printenv after the infected configuration file had maliciously set an altered PATH, and after clearenv had wiped that variable from the environment.)

Further reading

  • Theo de Raadt et al. (2014-11-01). OpenBSD 5.6 Changelog. OpenBSD.
  • Jonathan de Boyne Pollard (2018). "read-conf". Manual. nosh toolset. Softwares.
  • Jonathan de Boyne Pollard (2018). "clearenv". Manual. nosh toolset. Softwares.
  • Jonathan de Boyne Pollard (2018). "setenv". Manual. nosh toolset. Softwares.
  • Jonathan de Boyne Pollard (2018). "printenv". Manual. nosh toolset. Softwares.
3
  • Guilty !! I would have tought that only root can write /etc/os-release and root have no need to deploy malevolent script. Thoses seems fine manual you speak about ...
    – Archemar
    Commented Mar 31, 2018 at 16:38
  • How is this a solution? read-conf isn't available in GNU linux. At least I couldn't find it in any Arch or AUR package, nor noshell.
    – tralph3
    Commented Dec 3, 2021 at 1:49
  • 1
    I don't think sourcing violates the semantics of the file. The same part of the manual you quote says "It is possible to source the configuration from Bourne shell scripts," so we should be able to expect to use the file in this way. In practice I found that sometimes unquoted spaces make their way into the fields, so I'd recommend something like source <(sed 's/ /_/g' /etc/os-release)
    – m0dular
    Commented Apr 7, 2022 at 18:10
23

you can source the file and use var's value

. /etc/os-release
echo $ID
echo $VERSION

or try with awk

awk -F= '$1=="ID" { print $2 ;}' /etc/os-release

where

  • -F= tell awk to use = as separator
  • $1=="ID" filter on ID
  • { print $2 ;} print value
3
  • 3
    As JdeBP says, and thedailywtf.com points out regularly, source-ing files is a risky way to go about your day. Commented Aug 24, 2020 at 12:46
  • In debian like the value not contains quotes.
    – e-info128
    Commented Jul 1, 2021 at 17:30
  • 4
    @CarlWitthoft that's typical "security person" paranoia. If someone can write a malicious /etc/os-release, you've already been pwned in 100 other ways anyway. What real threat model exists where avoiding sourcing that file makes a meaningful difference? Commented Apr 16, 2022 at 21:07
10
ID=$(grep -oP '(?<=^ID=).+' /etc/os-release | tr -d '"')
VERSION=$(grep -oP '(?<=^VERSION_ID=).+' /etc/os-release | tr -d '"')
echo $ID:$VERSION

For the explanation see the other two great answers:

1
  • 1
    This solution addresses JdeBP's points about security while only requiring stock GNU command line tools. For these reasons I think it might be the best option.
    – Tristan
    Commented Dec 29, 2022 at 15:23
4

I recently had to do something similar and found this answer. Maybe this helps:

grep -ioP '^ID=\K.+' /etc/os-release

The very cool part about this is the \K. Simply, ignore it while parsing the regex in your head. Once you see a match in a line, then the result to be outputted on the terminal will start from right after the \K.

1
  • 3
    Welcome to the site, and thank you for your contribution. You may want to address the aspect of possible, but not mandatory, quotes around the "value" part of each line.
    – AdminBee
    Commented Sep 29, 2021 at 15:26
1

Heeding @JdeBP's recommendations not to blindly interpret any shell code in those files, but still handle quoting the way a shell would, in zsh, you could do that using its z and Q parameter expansion flags which are designed to perform shell tokenisation and quote removal:

contents=$(</etc/os-release)
shell_tokens=( ${(zZ[Cn])contents} )

set -o extendedglob
assignment_tokens=( ${(M)shell_tokens:#[[:IDENT:]]##=*} )
typeset -A field=()
for token ($assignment_tokens) field[${token%%=*}]=${(QX)token#*=}

Then, you can use $field[VERSION], $field[VERSION_ID]... in your zsh script.

  • $(<file): like in ksh: gets the contents of the file stripped of all trailing newline characters¹
  • ${(z)scalar} extracts all the shell tokens².
  • Z[Cn]: tune the z flag, here to strip shell comments and treat newline as just whitespace.
  • ${array:#pattern}: expand to the array elements except those matching the pattern. With the (M) flag, expands to only the Matching elements instead.
  • [[:IDENT:]]: characters allowed in a shell identifier.
  • ##: (with extendedglob): one or more of the preceding atom (like + in extended regexps).
  • ${(QX)var}: removes one layer of quoting (X to report errors in there).

¹ beware that contrary to ksh/bash, NUL bytes in there are preserved.

² top-level ones, i.e. not recursively if there are command substitutions for instance.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .