Что следует проверять при переписывании условий bash для sh или ash?

Вы можете использовать тот факт, что .*жадный:

sed 's/\(.*\)|/\1`/'

Или используйте:

sed 's/|\([^|]*\)$/`\1/'

Для сопоставления с |, за которым следует что-то, что не содержит |, до конца строки, как уже было показано Тоби, но этот подход работает только для замены отдельных символов.

Чтобы заменить его в многострочной строковой переменной оболочки, в GNU sedвы можете использовать параметр -z, который обрабатывает ввод как с разделителями NUL, а не с разделителями новой строки:

var=$(printf %s "$var" | sed -z '...')

Другим подходом может быть выполнение подстановки с использованием стандартных операторов раскрытия параметров:

case $var in
  (*'|'*) var=${var%'|'*}'`'${var##*'|'}
esac

Или с этими sedкомандами, переведенными в их эквивалент оболочки:

Вksh93:

var=${var/@(*)'|'/\1'`'}
var=${var/%'|'*([^'|'])/'`'\1}

Вbash(предполагается, что строка не содержит последовательностей байтов, не образующих допустимых символов в текущей локали):

re='(.*)\|(.*)'
[[ $var =~ $re ]] && var=${BASH_REMATCH[1]}'`'${BASH_REMATCH[2]}

Вzsh:

set -o extendedglob
var=${var/(#b)(*)'|'/$match[1]'`'}

var=${var/%(#b)'|'([^'|']#)/'`'$match[1]}
5
10.01.2021, 06:18
3 ответа

Я не согласен, что условия легче сделать с ((...))и [[...]](, предполагая, что это то, о чем вы говорите; обратите внимание, что эти операторы не являются специфическими для bash и происходят из ksh ), а не из стандартной команды [или test. У [[... ]]и ((... ))есть несколько собственных проблем¹, которые намного хуже, чем у [.

Если ваш [дает сбой с неожиданной ошибкой оператора , вероятно, вы неправильно используете оболочку (как правило, вы забыли указать расширение )], а не команду [.

Чтобы узнать, как правильно и переносимо использовать [/ test, лучше всего обратиться к его спецификации POSIX .

Несколько основных правил для обеспечения безопасности::

  • цитирует все расширения слов ($var, $(cmd),$((arithmetic)))в своих аргументах. Это относится ко всем командам, а не только к [и не только к dash. [[... ]]и ((... ))сами по себе являются специальными конструкциями со своим собственным специфическим синтаксисом (, варьирующимся от оболочки к оболочке ), где не всегда очевидно, когда что-то можно или нельзя заключать в кавычки.

  • не передавайте более 4 аргументов помимо [и ]. То есть не используйте устаревшие операторы -oи -a, а также (или )для группировки. Поэтому обычно ваше выражение [должно быть либо:

    • один аргумент, как в [ "$string" ], хотя я предпочитаю вариант [ -n "$string" ], чтобы явно указать, что вы проверяете $stringна не -пустое значение.
    • унарный оператор (-f, -r, -n... )и его операнд, которому необязательно предшествует !для отрицания.
    • бинарный оператор (=, -gt... )и его 2 операнда, которым может предшествовать!
    • Операнды арифметических операций должны быть десятичными целочисленными литеральными константами с необязательным знаком. dash, как bashпринимает начальные и конечные пробелы в этих операндах,но не все [реализации.

См. стандарт POSIX для получения списка переносимых операторов. Обратите внимание, что dashтакже имеет несколько расширений по сравнению со стандартом (-nt, -ef, -k, -O, <, >²...)

Для сопоставления с образцом используйте конструкцию case(case $var in ($pattern)...)вместо if [[ $var = $pattern ]]....

Для расширенного сопоставления регулярных выражений вы можете использоватьawk:

ere_match() { awk -- 'BEGIN{exit !(ARGV[1] ~ ARGV[2])}' "$1" "$2"; }
if ere_match "$string" "$regex"...

Вместо:

if [[ $string =~ $regex ]]...

Для операций И/ИЛИ соедините несколько [вызовов с операторами оболочки &&или ||(, которые имеют одинаковый приоритет )и используйте группы команд для группировки, такие же, как для любого другая команда, а не только [.

if
  [ "$mode" = "$mode1" ] || {
    [ -f "$file" ] && [ -r "$file" ]
  }
then...

вместо:

if [[ $mode = "$mode1" || ( -f $file && -r $file ) ]]; then...

Некоторые стандартные эквиваленты некоторых из bashs/ dashs/ zshs/ kshs/ yashs test/ [не -POSIX операторы:

  • -a file-e file(но обратите внимание, что оба возвращают false для символической ссылки, например, на недоступные файлы)

  • -k file

    has_t_bit() (export LC_ALL=C
      ls -Lnd -- "$1" 2> /dev/null | {
        unset -v IFS
        read mode rest &&
          case $mode in
            (*[Tt]) true;;
            (*) false;;
          esac
      }
    )
    
  • -O file->

    is_mine() (export LC_ALL=C
      ls -Lnd -- "$1" 2> /dev/null | {
        unset -v IFS
        read mode links fuid rest &&
          euid=$(id -u) &&
          [ "$fuid" -eq "$euid" ]
      }
    )
    
  • -G file

    is_my_group() (export LC_ALL=C
      ls -Lnd -- "$1" 2> /dev/null | {
        unset -v IFS
        read mode links fuid fgid rest &&
          egid=$(id -g) &&
          [ "$fgid" -eq "$egid" ]
      }
    )
    
  • -N file:Нет эквивалента, так как не существует API POSIX для получения с полной точностью времени модификации или доступа к файлу.

  • file1 -nt file2/file1 -ot file2

    newer() (export LC_ALL=C
      case $1 in ([/.]*) ;; (*) set "./$1" "$2"; esac
      case $2 in ([/.]*) ;; (*) set "$1" "./$2"; esac
      [ -n "$(find -L "$1" -prune -newer "$2" 2> /dev/null)" ]
    )
    older() { newer "$2" "$1"; }
    
    newer file1 file2
    

    будьте осторожны, поведение -nt/ -otзависит от реализации, если какой-либо файл недоступен. Здесь newerи olderвозвращают false в этих случаях.

  • file1 -ef file2

    same_file() (export LC_ALL=C
      [ "$1" = "$2" ] && [ -e "$1" ] && return
      inode1=$(ls -Lid -- "$1" | awk 'NR == 1 {print $1}') &&
        inode2=$(ls -Lid -- "$2" | awk 'NR == 1 {print $1}') &&
        [ -n "$inode1" ] && [ "$inode1" -eq "$inode2" ] &&
        dev1=$(df -P -- "$1" | awk 'NR == 2 {print $1}') &&
        dev2=$(df -P -- "$2" | awk 'NR == 2 {print $1}') &&
        [ -n "$dev1" ] && [ "$dev1" = "$dev2" ]
    ) 2> /dev/null
    
    same_file file1 file2
    

    (предполагается, что исходники файловой системы в выводе dfне содержат пробелов; сравнение файловых систем также не работает должным образом, если операнды на самом деле являются устройствами с смонтированными на них файловыми системами ).

  • -v varname[ -n "${varname+set}" ]

  • -o optnamecase $- in (*"$single_letter_opt_name"*).... Для длинного имени опции я не могу придумать стандартный эквивалент.

  • -R varname

    is_readonly() (export LC_ALL=C
      _varname=$1
      is_readonly_helper() {
        [ "${1%%=*}" = "$_varname" ] && exit
      }
      eval "$(
        readonly -p |
          sed 's/^[[:blank:]]*readonly/is_readonly_helper/'
      )"
      exit 1
    )
    

    Однако будьте осторожны, это опасно, так как sedможет зависеть от ограничения LINE _MAX, в то время как переменные не имеют ограничения длины.

    Также обратите внимание, что в ksh93-R nameнужно проверить, является ли nameпеременной nameref.

  • "$a" == "$b""$a" = "$b"

  • "$a" '<' "$b","$a" '>' "$b"

    collate() {
      awk -- 'BEGIN{exit !(ARGV[1] "" '"$2"' ARGV[2] "")}' "$1" "$3"
    }
    collate "$a" '<' "$b"
    collate "$a" '>' "$b"
    

    (, collateтакже поддерживает <=, >=, ==,!=).

    Сравнивает ли [или awk's <строки, используя сравнение байтов -с -байтами, или порядок сопоставления локали зависит от реализации. dashне был интернационализирован, поэтому работает только с байтовыми значениями. Для yash[это основано на сопоставлении локали. Для bash, [[ $a < $b ]]работает с сопоставлением локали, а [ "$a" '<' "$b" ]работает с байтовым значением. POSIX требует, чтобы awkи <использовали порядок сортировки локали, но некоторые реализации awk, такие как mawk, не были интернационализированы, поэтому работают с байтовыми значениями. Чтобы принудительно сравнить значение байта -, установите языковой стандарт на C.

    Для операторов awk, ==и !=POSIX раньше требовал использования сопоставления, но лишь немногие реализации делают это, и спецификация POSIX теперь оставляет его неуказанным. [=и !=всегда выполняют сравнение байтов -с -, но см. yash===/ !==ниже.

  • "$a" '=~' "$b"см. выше (в то время как в bash/ kshоператор =~доступен только в [[...]], это не относится к zshили yash, чей [поддерживает его ).

  • "$string" -pcre-match "$pcre"Нет эквивалента, поскольку PCRE не указаны в POSIX.

  • "$a" === "$b"/"$a" !== "$b"(strcoll()сравнение)expr "z $a" = "z $b" > /dev/null/expr "z $a" != "z $b"(также обратите внимание, что некоторые awkреализации ==/ !=оператор также используют strcoll()для сравнения ).

  • -o "?$option_name"(является допустимым именем параметра )нет стандартного эквивалента, о котором я могу думать, поскольку выходной формат set -oили set +oне указан.

  • "$version1" -veq "$version2"-vne/ -vgt/ -vge/ -vlt/ -vleдля сравнения номеров версий)

    version_compare() {
      awk -- '
        function pad(s,   r) {
          r = ""
          while (match(s, /[0123456789]+/)) {
            r = r substr(s, 1, RSTART - 1) \
                sprintf("%020u", substr(s, RSTART, RLENGTH))
            s = substr(s, RSTART + RLENGTH)
          }
          return r s
        }
        BEGIN {exit !(pad(ARGV[1]) '"$2"' pad(ARGV[2]))}' "$1" "$3"
    }
    

    используется какversion_compare "$v1" '<' "$v2"(или <=, ==, !=, >=, >),используя то же значение, что и операторы yash(или zshчисловой порядок ), и предполагая, что ни одно из чисел не имеет более 20 цифр.

  • Оболочка bosh[также имеет -D fileи -P fileдля проверки того, является ли файл дверью или портом событий . Это типы файлов, специфичные для Solaris, поэтому они не охватываются POSIX, но вы всегда можете определить:

    is_of_type() (export LC_ALL=C
      case "$2" in
        ([./]*) file="$2";;
            (*) file="./$2"
      esac
      [ -n "$(find -H "$file" -prune -type "$1")" ]
    )
    

    Затем вы можете выполнить is_of_type D "$file", что, хотя и не POSIX, вероятно, будет работать в Solaris, где двери имеют значение, или эквивалентно в других системах и их собственных конкретных типах файлов.


¹ см., например, уязвимости внедрения команд в арифметические операторы [[...]]и ((...)), путаницу с оператором =~в bash3.2+, ksh93 или yash.

² некоторые из них довольно широко распространены среди [реализаций и могут быть добавлены в будущую версию стандарта POSIX

17
18.03.2021, 22:37

Я бы сказал, что важнее всего избегать [[... ]]. Арифметика между ((... )), вероятно, тоже не работает.

Сделайте привычкой заключать переменные в кавычки, чтобы избежать ошибок, когда переменная не установлена, например "$var".

Все операторы, арифметические или логические, представляют собой слова с тире, такие как -eqили -aвместо =или &&.

Также используйте shellcheck.net для проверки синтаксиса. #!/bin/shсообщает shellcheck, что вы хотите использовать оболочку POSIX, которая, по-моему, не совсем оболочка Bourne, но близка к ней. Также должен быть способ протестировать сценарии оболочки Bourne.

-2
18.03.2021, 22:37

Stéphane Chazelas дал превосходный ответ, указав на различия в синтаксисе между узко совместимыми с POSIX -оболочками и другими оболочками, а также на другие подводные камни. Этот ответ будет использовать другой подход.

Человеку может быть сложно взять сценарий, написанный для bash, kshили какой-либо другой оболочки с их расширениями, отличными от -POSIX, и безопасно преобразовать весь сценарий в сценарий, используя только синтаксис POSIX. Легко случайно что-то пропустить, и тогда скрипт может дать сбой, причем, возможно, в очень неподходящий момент.

Существует замечательный инструмент с открытым исходным кодом, который я использую в течение некоторого времени, он называется shellcheck . Что делает shellcheck, так это то, что он работает как линтер для сценариев оболочки. Если вы укажете шебанг, например #!/usr/bin/env sh, или используете опцию --shell sh, shellcheck автоматически предупредит вас о любом синтаксисе, который недопустим для указанной оболочки.

У Shellcheck также есть очень хорошая вики, где каждая ошибка или предупреждение линтинга задокументированы на отдельной странице с объяснением того, что означает ошибка или предупреждение, и предложениями по правильному/более безопасному коду.

Если вы решите установить shellcheck,Я рекомендую вам установить этот последний стабильный выпуск со страницы выпусков на GitHub , а не использовать менеджер пакетов, поскольку, по моему опыту, менеджеры пакетов имеют старые версии shellcheck.

10
18.03.2021, 22:37

Теги

Похожие вопросы