Proměnné v $hellu

Lukáš Bařinka
LinuxDays 2023

Proměnné v $hellu

Lukáš Bařinka
LinuxDays 2023

Obsah

Co je to proměnná

Parametr je entita, která ukládá hodnoty.
Proměnná je parametr označený jménem.
Proměnná má hodnotu a nula nebo více atributů.

man bash /^PARAMETERS
  • Vypsání hodnoty proměnné echo $BASH_VERSION 5.2.15(1)-release
  • Substituce proměnné $BASH /usr/bin/bash
  • Bezpečný výpis hodnoty proměnné printf '%s\n' "$EDITOR" /usr/bin/vim
  • Výpis hodnoty proměnné včetně atributů declare -p EDITOR declare -x EDITOR="/usr/bin/vim"
  • Výpis všech proměnných a jejich hodnot declare -p set

Vytvoření, změna a zrušení proměnné

name=[value]

Všechny hodnoty podléhají:

  • expanzi vlnovky ~,
  • expanzi parametrů a proměnných $VAR,
  • substituci příkazů $(cmd),
  • aritmetické expanzi $((1+1)) a
  • odstranění uvozovek VAR="foo" .
man bash /^PARAMETERS
T.j., není tam rozdělení na slova.
  • Přiřazení hodnoty VAR=foo
  • Vyprázdnění hodnoty VAR=
  • Ostranění proměnné unset VAR
  • Formátování hodnoty a uložení do proměnné printf -v RANGE '[%04d:%04d]' "$min" "$max"
  • Vytvoření proměnné (včetně atributů) declare -x VAR="foo bar"

Použití proměnné

$VAR ${VAR}
"${VAR}"

Reference na proměnnou

set -- foo bar baz
for (( i=1; i<=$#; i++ )); do
  printf '$%d = %s\n' "${i}" "${!i}"
done
$1 = foo
$2 = bar
$3 = baz

Substituce proměnné

  • Délka proměnné max=1234 i=56
    printf '%0*d\n' "${#max}" "${i}"
    0056
  • (Prázdná/neexistující) hodnota proměnné
    ${parameter:-word} - použít hodnotu
    ${parameter:=word} - přiřadit hodnotu
    ${parameter:?word} - zobrazit chybu
    ${parameter:+word} - použít alt. hodnotu
    file=${1:-default.conf}
    : ${output:=/dev/null}
  • Podřetězce file=/var/log/service.log.gz echo ${file:5} log/service.log.gz echo ${file:5:4} log/ echo ${file: -3} .gz echo ${file: -7:-2} .log.
  • Ořez file=/var/log/service.log.gz echo ${file#/var/} log/service.log.gz echo ${file##*/} service.log.gz echo ${file%.gz} /var/log/service.log echo ${file%%.*} /var/log/service
  • Náhrady/modifikace hodnoty file=/var/log/service.log.gz echo ${file/service/srv} /var/log/srv.log.gz echo ${file//\//:} :var:log:service.log.gz echo echo ${file/#\/*\///tmp/} /tmp/service.log.gz echo ${file/%log*/foo} /var/foo
  • Změna velikosti znaků (case) file=service.log.gz ${file^} Service.log.gz ${file^^} SERVICE.LOG.GZ file=README.md ${file,} file=rEADME.md ${file,,[AEIOUY]} file=ReaDMe.md
  • Transformace proměnné echo ${IFS@Q} $' \t\n'Quoted X='x\ty\bz'; echo "${X@E}" x       zEscape echo ${X@A} X='x\ty\bz'Assignment echo "${PS1}" ${debian_chroot:+($debian_chroot)}\u@\h:\w$(__git_ps1)\$ echo "${PS1@P}" barinkl@ThinPad:~ (master)$Prompt a=( foo bar 'b a z' ); echo "${a[@]@K}" 0 "foo" 1 "bar" 2 "b a z"Key-value echo ${PATH@a} xAttributes
  • Výpis jmen proměnných podle prefixu printf '%s\n' "${!BASH*}" BASH BASHOPTS BASHPID BASH_ALIASES BASH_ARGC BASH_ARGV BASH_ARGV0 BASH_CMDS BASH_COMMAND BASH_COMPLETION_VERSINFO BASH_LINENO BASH_LOADABLES_PATH BASH_SOURCE BASH_SUBSHELL BASH_VERSINFO BASH_VERSION printf '%s\n' "${!BASH@}" BASH
    BASHOPTS
    BASHPID
    BASH_ALIASES
    ...
  • Klíče prvků pole printf '%s\n' "${!BASH_ALIASES[*]}" ll la df ls bye ... printf '%s\n' "${!BASH_ALIASES[@]}" ll
    la
    df
    ls
    bye
    ...

Neexistující proměnná

  • Hodnota neexistující proměnné je prázdná ... | $I18N sort ... | "$I18N" sort bash: : command not found
  • Zákaz používání neinicializovaných proměnných set -u; A=$foo bash: foo: unbound variable A=${foo:-bar}

Skalární (falešné) pole

  • foo="$foo bar"; foo+=" baz"
    for i in $foo; do ...; done
  • IFS=:; for i in $PATH; do echo "$i"; done
    printf '%s\n' $PATH
    /usr/local/bin
    /usr/bin
    /bin
    ...
    echo $PATH /usr/local/bin /usr/bin /bin ...
  • IFS=$' \t\n'

Testy proměnných

  • Řetězcová hodnota
    [[ -z $var ]] - prázdná hodnota
    [[ $var > text ]] - abecední řazení
    [[ $var == SP* ]] - shellovský vzor
    [[ $var =~ ^RE ]] - regulární výraz
  • Číselná hodnota
    [[ $var -gt 5 ]] - číselné porovnání
    (( var > 5 )) - aritmetika
  • Proměnná
    [[ -v var ]] - existence proměnné
    [[ -R var ]] - proměnná typu reference

Pořadí expanzí

min=1; max=10; echo {$min..$max} {1..10} pattern=*.conf; echo /etc/$pattern /etc/adduser.conf /etc/apg.conf /etc/appstream.conf /etc/ca-certificates.conf /etc/cloudprint.conf /etc/daemon.conf /etc/debconf.conf /etc/deluser.conf /etc/discover-modprobe.conf ...

There are seven kinds of expansion performed:

  1. Brace expansion
  2. Tilde expansion
  3. Parameter and variable expansion
  4. Command substitution
  5. Arithmetic expansion
  6. Word splitting
  7. Pathname expansion
man bash /^EXPANSION

Typy proměnných

Deklarace

declare typeset local readonly export
  • -x  proměnná prostředí (export)
  • -a  číslované pole (array)
  • -A  asociativní pole (associative array)
  • -i  celé číslo (integer)
  • -n  odkaz na proměnnou (name-ref)
  • -r  pro čtení (read-only)
  • -t  sledování proměnné (trace)
  • -l  malé písmena (lower-case)
  • -u  velká písmena (upper-case)
  • -g  globální proměnná (global)
  • Proměnná prostředí (export) X=foo Y=bar; export X
    bash -c 'declare -p X Y'
    declare -x X="foo"
    bash: line 1: declare: Y: not found
  • Číslované pole (array) declare -p BASH_VERSINFO declare -ar BASH_VERSINFO=([0]="5" [1]="2" [2]="15" [3]="1" [4]="release" [5]="x86_64-pc-linux-gnu")
  • Konstanta (read-only) BASH_VERSINFO=foo bash: BASH_VERSINFO: readonly variable
  • Asociativní pole (associative array)
    Jako jediné je nutné deklarovat předem declare -A array
    bash -norc
    BASH_ALIASES=([d]='date' [t]='d +%T')
    BASH_ALIASES+=([D]='d +%F')
    alias
    alias D='d +%F'
    alias d='date'
    alias t='d +%T'
    d;D;t Sat Oct 7 10:30:00 CEST 2023
    2023-10-07
    10:30:00
  • Celé číslo (integer) declare -i X; X=1+2*3; declare -p X declare -i X="7"
  • Odkaz na proměnnou (name-ref) foo=1 bar=2 baz=3
    declare -n item
    for item in foo bar baz BASH_ALIASES[d]; do
      echo "$item from ${!item}"
    done
    1 from foo
    2 from bar
    3 from baz
    date from BASH_ALIASES[d]
  • Malá/velká písmena (lower/upper-case) declare -u foo; declare -l bar
    foo=AbCdEfGh bar=$foo
    declare -p foo bar
    declare -u foo="ABCDEFGH"
    declare -l bar="abcdefgh"

Poziční parametry

./my_script-ab-Cfoobar
$0$1$2$3$4
  • Nastavení/změna zevnitř skriptu set -- 'f o o' bar baz $0:bash   $1:f o o   $2:bar   $3:baz   $#:3
  • Posun shift $0:bash   $1:bar   $2:baz   $#:2 shift 2 $0:bash   $#:0
  • Pole pozičních parametrů: $* $@ $* f o o bar baz $@ f o o bar baz "$*" f o o bar baz "$@" f o o bar baz
  • Průchod polem parametrů for i in "$@"; do ... "$i"; done while (( $# )); do ... "$1"; shift; done

Pole a indexy

declare -A color
color=( red '#FF0000' green '#00FF00' )
color[blue]='#0000FF'
echo "${color[@]}" #0000FF #FF0000 #00FF00 IFS=/; printf '%s\n' "${color[*]}" #0000FF/#FF0000/#00FF00 for c in "${!color[@]}"; do
  echo "${c} = ${color[$c]#\#}"
done
blue = 0000FF
red = FF0000
green = 00FF00

Rozdělení na slova a expanze souborů
při přiřazení do proměnné

  • U skalárních proměnných nedává smysl,
    proto neprobíhá dirs=/l*/; alpha={a..e}; IFS=:; path=$PATH
    declare -p dirs alpha path
    declare -- dirs="/l*/"
    declare -- alpha="{a..e}"
    declare -- path="/usr/local/bin:/usr/bin:/bin:..."
    echo $dirs /lib/ /libx32/ /lib32/ /lib64/ /lost+found/
  • Pole může obsahovat více hodnot,
    proto probíhá dirs=( /l*/ ); alpha=( {a..e} )
    IFS=:; path=( $PATH )
    declare -p dirs alpha path
    declare -a dirs=([0]="/lib/" [1]="/libx32/" [2]="/lib32/" [3]="/lib64/" [4]="/lost+found/")
    declare -a alpha=([0]="a" [1]="b" [2]="c" [3]="d" [4]="e")
    declare -a path=([0]="/usr/local/bin" [1]="/usr/bin" [2]="/bin" [3]=...)

Auto-indexace číslovaného pole

alpha=( {a..z} )
declare -p alpha
declare -a alpha=([0]="a" [1]="b" [2]="c" [3]="d" [4]="e" [5]="f" [6]="g" [7]="h" [8]="i" [9]="j" [10]="k" [11]="l" [12]="m" [13]="n" [14]="o" [15]="p" [16]="q" [17]="r" [18]="s" [19]="t" [20]="u" [21]="v" [22]="w" [23]="x" [24]="y" [25]="z") alpha=( [64]='' "${alpha[@]^^}" )
unset 'alpha[64]'
declare -p alpha
declare -a alpha=([65]="A" [66]="B" [67]="C" [68]="D" [69]="E" [70]="F" [71]="G" [72]="H" [73]="I" [74]="J" [75]="K" [76]="L" [77]="M" [78]="N" [79]="O" [80]="P" [81]="Q" [82]="R" [83]="S" [84]="T" [85]="U" [86]="V" [87]="W" [88]="X" [89]="Y" [90]="Z")

Viditelnost proměnných

Export vs subshell

  • Proměnné jsou viditelné pouze z procesu, kde jsou deklarované foo=bar; bash -c 'declare -p foo' bash: line 1: declare: foo: not found
  • Při vytváření potomka procesu se dědí (proměnné) prostředí (environment) env | sort COLORTERM=truecolor
    COLUMNS=80
    DISPLAY=:0
    EDITOR=/usr/bin/vim
    ...
  • Atribut/příkaz export (-x) přidá proměnnou mezi proměnné prostředí a tím ji zdědí i potomek declare -x foo=bar
    export x=10
    y=20 bash -c 'declare -p foo x y'
    declare -x foo="bar"
    declare -x x="10"
    declare -x y="20"
  • Automatický export všech nových nebo upravených proměnných set -a
    set -o allexport
  • Žádná proměnná z potomka (ani exportovaná) se nedostane k rodiči (volání hodnotou) export foo=bar; bash -c 'foo=baz'
    declare -p foo
    declare -x foo="bar"

Návratová hodnota a
dědění FD v subshellu

  • Proces vrací pouze návratovou hodnotu (uložena do proměnné $?) bash -c 'sleep 10; exit 7' &
    echo $?; wait $!; echo $?
    [1] 606475
    0
    [1]+ Exit 7         bash -c 'sleep 10; exit 7'
    7
  • Potomek sdílí (zdědí) se svým rodičem file-descriptory (otevřené soubory) date +%F 2023-10-07 D=$( date +%F ); declare -p D declare -- D="2023-10-07" foo=10
    foo=$( bash -c 'echo $(($1+1))' -- "$foo" )
    declare -p foo
    declare -x foo="11" foo=$( expr "$foo" + 1 )

Funkce a lokální proměnné

  • Proměnné jsou v základu globální, tedy dostupné odkudkoliv z procesu f() { (( x++ )); }
    x=10; f; declare -p x
    declare -- x="11"
  • Parametry funkce jsou předávány hodnotou do pozičních parametrů inc() { echo $(( $1 + 1 )); }
    x=10; inc "$x"; declare -p x
    11
    declare -- x="10"
  • Aby nedošlo k přepsání globální hodnoty,
    měla by funkce používat lokální proměnné,
    které překryjí uvnitř funkce ty globální name() {
      local file="$1"
      file=${file##*/}
      file=${file%%.*}
      printf '%s\n' "$file"
    }
    file='/var/log/apache2/error.log.1'
    echo "$file => $( name "$file" )" /var/log/apache2/error.log.1 => error

Předávání parametrů z/do funkce odkazem

  • Zvolení proměnné pro návratovou hodnotu inc() {
      declare -g ret;
      (( ret = $1 + 1 ))
    }
    x=10; inc "$x"; x=$ret; declare -p x
    declare -- x="11"
  • Jméno globální proměnné jako parametr funkce inc() {
      ret=$1
      (( $ret = ${!1} + 1 ))
    }
    x=10; inc x; declare -p x
    declare -- x="11"
  • Jméno globální proměnné jako parametr funkce (name-ref) inc() {
      declare -n ret=$1
      (( ret = $1 + 1 ))
    }
    x=10; inc x; declare -p x
    declare -- x="11"

Funkce vs Skript

Skript f() (...) F() {...}
Oddělený proces
Zůstává nastavení
Neexportované proměnné
Globální proměnné
Sdílené FD
echo $$ 638557 bash -c 'declare -p PPID BASHPID BASH_SUBSHELL' declare -ir PPID="638557"
declare -i BASHPID="639248"
declare -- BASH_SUBSHELL="0"
f()(declare -p PPID BASHPID BASH_SUBSHELL); f declare -ir PPID="161430"
declare -i BASHPID="638718"
declare -- BASH_SUBSHELL="1"
F(){ declare -p PPID BASHPID BASH_SUBSHELL;}; F declare -ir PPID="161430"
declare -i BASHPID="638557"
declare -- BASH_SUBSHELL="0"

Problémy a omezení

Uložení hodnoty do souboru a opětovné načtení

  • Expanze subshellu (command substitution) odstraní znaky nové řádky z konce výstupu,
    což je obvykle užitečné echo "File $f has $( wc -l <$f ) lines"
  • Problém je, pokud potřebujeme kompletní výstup uložit do proměnné content=$( printf '\n\n\n' )
    declare -p content
    declare -- content=""
  • Přidat libovolný znak na konec výstupu content=$( printf '\n\n\n'; echo . )
    declare -p content
    declare -- content=$'\n\n\n.'
  • Odstranit poslední znak z proměnné printf '%s' "${content%?}" | od -c 0000000   \n   \n   \n
    0000003
    content=${content%?}
  • Uložit proměnnou do souboru printf '%s.' "$content" >save
  • Načíst proměnnou ze souboru a odstranit poslední znak content=$( <save ); content=${content%?}
  • Uložit celou deklaraci proměnné declare -p content >save
  • Načíst obsah souboru jako skript source save

Uložení binárních dat

  • Binární nula (\0) je ukončovač hodnoty var=$'a\tb\nc\0d'; declare -p var declare -- var=$'a\tb\nc'
  • Uložení binárních dat do proměnné bin=$( printf 'a\tb\nc\0d' | base64 )
    declare -p bin
    declare -- bin="YQliCmMAZA=="
  • Použití binárních dat z proměnné base64 -d <<<"$bin" | od -c 0000000   a   \t   b   \n   c   \0   d
    0000007

Neexistující export polí

  • Proměnné prostředí jsou pouze skalární
  • Strukturovaná data zakódovat jako skalární a v subshellu dekódovat (TXT/CSV/JSON/YAML/...)
  • Uložit deklaraci polí do exportované proměnné a v subshellu vyhodnotit deklaraci
alpha=( [65]=A {B..Z} )
declare -A color=(
  [red]='#FF0000'
  [green]='#00FF00'
  [blue]='#0000FF'
)
export vars=$( declare -p alpha color )

bash -c 'eval "$vars"; declare -p alpha color'
declare -a alpha=([65]="A" [66]="B" [67]="C" [68]="D" [69]="E" [70]="F" [71]="G" [72]="H" [73]="I" [74]="J" [75]="K" [76]="L" [77]="M" [78]="N" [79]="O" [80]="P" [81]="Q" [82]="R" [83]="S" [84]="T" [85]="U" [86]="V" [87]="W" [88]="X" [89]="Y" [90]="Z")
declare -A color=([blue]="#0000FF" [red]="#FF0000" [green]="#00FF00" )

?


https://lukasbarinka.gitlab.io/ld23