Вы можете использовать тот факт, что .*
жадный:
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]}
Я не согласен, что условия легче сделать с ((...))
и [[...]]
(, предполагая, что это то, о чем вы говорите; обратите внимание, что эти операторы не являются специфическими для 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...
Некоторые стандартные эквиваленты некоторых из bash
s/ dash
s/ zsh
s/ ksh
s/ yash
s 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 optname
case $- 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
Я бы сказал, что важнее всего избегать [[... ]]
. Арифметика между ((... ))
, вероятно, тоже не работает.
Сделайте привычкой заключать переменные в кавычки, чтобы избежать ошибок, когда переменная не установлена, например "$var"
.
Все операторы, арифметические или логические, представляют собой слова с тире, такие как -eq
или -a
вместо =
или &&
.
Также используйте shellcheck.net для проверки синтаксиса. #!/bin/sh
сообщает shellcheck, что вы хотите использовать оболочку POSIX, которая, по-моему, не совсем оболочка Bourne, но близка к ней. Также должен быть способ протестировать сценарии оболочки Bourne.
Stéphane Chazelas дал превосходный ответ, указав на различия в синтаксисе между узко совместимыми с POSIX -оболочками и другими оболочками, а также на другие подводные камни. Этот ответ будет использовать другой подход.
Человеку может быть сложно взять сценарий, написанный для bash
, ksh
или какой-либо другой оболочки с их расширениями, отличными от -POSIX, и безопасно преобразовать весь сценарий в сценарий, используя только синтаксис POSIX. Легко случайно что-то пропустить, и тогда скрипт может дать сбой, причем, возможно, в очень неподходящий момент.
Существует замечательный инструмент с открытым исходным кодом, который я использую в течение некоторого времени, он называется shellcheck . Что делает shellcheck, так это то, что он работает как линтер для сценариев оболочки. Если вы укажете шебанг, например #!/usr/bin/env sh
, или используете опцию --shell sh
, shellcheck автоматически предупредит вас о любом синтаксисе, который недопустим для указанной оболочки.
У Shellcheck также есть очень хорошая вики, где каждая ошибка или предупреждение линтинга задокументированы на отдельной странице с объяснением того, что означает ошибка или предупреждение, и предложениями по правильному/более безопасному коду.
Если вы решите установить shellcheck,Я рекомендую вам установить этот последний стабильный выпуск со страницы выпусков на GitHub , а не использовать менеджер пакетов, поскольку, по моему опыту, менеджеры пакетов имеют старые версии shellcheck.