Скажите df
, что выводить:
df -h --output=avail | tail -n1
Note: this might not be portable to all *nixes.
Проблема заключается в том, что в арифметическом выражении оболочки, например, внутри$((...))
(POSIX )или((...))
(ksh/bash/zsh ), индексов массива или аргументов некоторых встроенных функций оболочки или [[...]]
операнды, разложения слов (${param}
, $((...))
, $[...]
, $(...)
, `...`
,${...; }
)выполняются сначала , а затем полученный текст интерпретируется как арифметическое выражение.
В случае $((...))
это даже требование POSIX.
Это позволяет работать таким вещам, как op=+; echo "$(( 1 $op 2 ))"
, и это объясняет, почему a=1+1; echo "$(($a * 2))"
выводит 3
вместо 4
, поскольку вычисляется выражение 1+1 * 2
.
Это также частично почему использование непроверенных данных в арифметических выражениях является уязвимостью системы безопасности в целом.
Что легко упустить из виду, так это то, что это также применимо к таким вещам, как
(( assoc[$var]++ ))
Выше, за исключением ksh93
, сначала раскрывается $var
, а результат интерпретируется.
Это означает, что если $var
содержит @
или *
, то вычисляются выражения assoc[@]++
или assoc[*]++
, и @
/ *
имеют там особое значение. Если $var
равно x] + 2 + assoc[y
, это становится assoc[x] + 2 + assoc[y]
.
Теперь обычно, в $(( $var ))
, даже если $var
содержит что-то вроде $(reboot)
, второго раунда расширения не происходит, reboot
не будет запускаться. Но, как уже было замечено в Последствия для безопасности использования непроверенных данных в арифметической оценке Shell , есть исключение, если оно появляется внутри word[...]
, чтобы разрешить рекурсивное расширение. В основе проблемы лежит неудачная особенность оболочки Korn, согласно которой, если var
содержит арифметическое выражение, то в $((var))
арифметическое выражение в $var
вычисляется, даже рекурсивно (например, когда var2='var3 + 1' var='var2 + 1'
), что-то разрешено, но не требуется POSIX.
Поскольку это распространяется на элементы массива, это означает, что содержимое индексов массива в конечном итоге оценивается рекурсивно. Итак, если $var
равно $(reboot)
,затем (( assoc[$var]++ ))
вызывает reboot
.
ksh93
, кажется, имеет некоторый уровень работы, но только тогда, когда $var
не содержит $
. Итак, хотя ksh93 работает с var=']'
, var='@'
или var='`reboot`'
, это не так с $(reboot)
.
Например, если мы заменим reboot
безвреднымuname>&2
:
$ var='1$(uname>&2)' ksh -c 'typeset -A a; (( a[$var]++ )); typeset -p a'
Linux
typeset -A a=([1]=1)
$ var='1$(uname>&2)' bash -c 'typeset -A a; (( a[$var]++ )); typeset -p a'
Linux
Linux
declare -A a=([1]="1" )
$ var='1$(uname>&2)' zsh -c 'typeset -A a; (( a[$var]++ )); typeset -p a'
Linux
Linux
typeset -A a=( [1]=1 )
Команда uname
в конечном итоге выполняется (дважды в bash
и zsh
, я полагаю, один раз для получения текущего значения и второй раз для выполнения назначения ).
В версии 5.0 в bash добавлен параметр assoc_expand_once
, который изменяет поведение:
$ var='1$(uname>&2)' bash -O assoc_expand_once -c 'typeset -A a; ((a[$var]++)); typeset -p a'
declare -A a=(["1\$(uname>&2)"]="1" )
теперь в порядке, но не устраняет проблемы с символами @
, *
или ]
, поэтому не устраняет уязвимость выполнение произвольной команды :
$ var='x]+b[1$(uname>&2)' bash -O assoc_expand_once -c 'typeset -A a; ((a[$var]++)); typeset -p a'
Linux
declare -A a
(на этот раз uname
выполняется как часть оценки простого массива(b
)оценки индекса ).
Список проблемных символов зависит от оболочки. $
— задача для всех трех, \
, `
, [
и ]
— задача для bash
и zsh
, "
, '
— для bash
. То же самое для @
и *
и пустых значений. Также обратите внимание, что в некоторых локалях кодировка некоторых символов действительно содержит кодировку \
, [
или ]
, по крайней мере, и может вызвать проблемы. Как их избежать, нужно делать по-разному во всех трех оболочках.
Чтобы обойти это, можно:
assoc[$var]=$(( ${assoc[$var]} + 1 ))
вместо этого. То есть:
=
, ++
, --
, +=
, /=
... арифметические операторы с членом ассоциативного массива в качестве цели. assoc[$var]
, а${assoc[$var]}
(или $assoc[$var]
в zsh
),или (${assoc[$var]})
, если это должно содержать арифметическое выражение, а не просто число. Но, как всегда, значение этого члена ассоциативного массива должно находиться под вашим контролем, желательно простое число, и, как и для любого другого раскрытия параметра, предпочтительно помещать вокруг него пробелы. Например, ((1 - $var))
предпочтительнее ((1-$var))
, так как последнее вызовет проблемы с отрицательными значениями.(((1--1))
вызывает синтаксическую ошибку в некоторых оболочках, так как это оператор --
, применяемый к 1
.
Другое предостережение заключается в том, что когда $var
пусто, в (( 1 + var ))
var
по-прежнему является токеном в синтаксисе арифметического выражения и соответствующим значением, если 0
. Но в (( 1 + $var ))
арифметическое выражение становится 1 +
, что является синтаксической ошибкой.((( $var + 1 ))
в порядке, хотя это становится + 1
, вызывая унарный оператор +
).
Другие подходы с bash
(, когда для параметра assoc_expand_once
установлено не включено )или zsh
(, но не ksh93
, что по-прежнему имеет проблему с символами ]
и \
. ), заключаются в том, чтобы отложить расширение до той второй, рекурсивной интерпретации, упомянутой выше.
(( assoc[\$var]++ ))
let 'assoc[$var]++'
(обязательно используйте одинарные кавычки здесь)incr='assoc[$var]++'; (($incr))
(или даже((incr))
)((' assoc[$var]++ '))
или(( assoc['$var']++ ))
(bash
только ). Те имеют преимущество или сохраняют статус выхода, полученный в результате арифметической оценки(успех , если не -ноль ), поэтому можно делать такие вещи, как:
if (( assoc[\$var]++ )); then
printf '%s\n' "$var was already seen"
fi
Остается одна проблема, связанная с bash
оболочкой.:bash
Ассоциативные массивы не поддерживают пустые ключи. В то время как assoc[]=x
не работает как в bash
, так и в zsh
(, а не в ksh93
), assoc[$var]
, когда $var
пусто, работает в zsh
или ksh93
, но не bash
. Даже zsh
assoc+=('' value)
, теперь поддерживаемый bash -5.1, не работает в bash
.
Итак, если вы работаете конкретно с bash
и если пустой ключ является одним из возможных значений,единственный вариант — добавить фиксированный префикс/суффикс. Так что используйте, например,:
assoc[.$var]=$(( ${assoc[.$var]} + 1 ))
Или:
let 'assoc[.$var]++'
(( assoc[.\$var]++ ))
...