Bash: пуленепробиваемые однострочные для управления переменными, подобными PATH - PullRequest
0 голосов
/ 13 апреля 2020

Я ищу четыре простые пуленепробиваемые , максимально короткие Bash функции:

PrependPath name path [path ...]
AppendPath name path [path ...]
RemovePath name path [path ...]
UniquePath name

, которые соответственно:

  • Предварительно добавьте путь (и) к переменной Bash, указанной в name, если эти пути еще не содержатся где-то в этой переменной,
  • Добавьте путь (и) к указанной переменной Bash на name, если эти пути не содержатся где-то в этой переменной,
  • Удалить все экземпляры данного пути (ей) из переменной Bash, указанной в name,
  • Отфильтруйте все, кроме первого (крайнего левого), вхождения любых дубликатов в переменной.

Например (я не забыл ни одного знака $):

export PATH=/bin:/usr/bin:/bin
PrependPath PATH /usr/local/bin  # --> PATH=/usr/local/bin:/bin:/usr/bin:/bin
AppendPath PATH /usr/local/bin   # --> PATH=/usr/local/bin:/bin:/usr/bin:/bin  (nothing changed, as was already there from PrependPath)
RemovePath PATH /usr/local/bin   # --> PATH=/bin:/usr/bin:/bin
UniquePath PATH                  # --> PATH=/bin:/usr/bin

Получив эти функции, мы можем использовать их для любых переменных пути, следуя тем же (или очень похожим правилам) переменной PATH, как, например, LD_LIBRARY_PATH, LIBRARY_PATH, INFOPATH, .. .

Что затрудняет то, что я хочу, чтобы эти функции работали для абсолютно каждый * 10 32 * допустимый путь, который возможен в PATH, который в основном исключает символы \0, / и :, но допускает все других символов (при условии, что он корректно закодирован в UTF-8 ), включая, в частности, пробелы и переводы строк.

Я хотел бы сохранить философское обсуждение «никто в здравом уме никогда не будет использовать такой путь» из этого. Если бы я мог на законных основаниях создать такой путь, и было бы законно поместить его в PATH, то это справедливая игра.

Еще несколько примечаний:

  • Функция не должна изменяться родительская среда любым способом (например, добавление к нему функций / переменных), кроме экспорта / изменения указанной переменной пути.
  • Функция не должна приравнивать пути как /usr/ и /usr, которые отличаются только косыми чертами.
  • Функция должна правильно обрабатывать пустые пути (с указанием текущего каталога), не случайно добавляя их в переменную пути, если их там нет, и не удаляя их случайно если они уже есть .
  • Функция должна работать, даже если PATH равен пуст . Эта ситуация не может быть исключена как невозможная.

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

function PrependPath() { TODO }
function AppendPath() { TODO }
function RemovePath() { TODO }
function UniquePath() { TODO }

function Assert() { ((i++)); [[ "$TESTPATH" == "$1" ]] && echo "$i) SUCCESS" || echo "$i) FAILURE => Got >$TESTPATH<"; }

i=0
echo "Test: PrependPath"

TESTPATH="/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/"
PrependPath TESTPATH /usr/sbin
Assert "/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/"
PrependPath TESTPATH /bin
Assert "/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/"
PrependPath TESTPATH /usr/slash
Assert "/usr/slash:/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/"
PrependPath TESTPATH /new
Assert "/new:/usr/slash:/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/"
PrependPath TESTPATH /new
Assert "/new:/usr/slash:/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/"
PrependPath TESTPATH ""
Assert ":/new:/usr/slash:/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/"
PrependPath TESTPATH ""
Assert ":/new:/usr/slash:/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/"
PrependPath TESTPATH "/usr"
Assert "/usr::/new:/usr/slash:/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/"

TESTPATH=
PrependPath TESTPATH /multiple /new
Assert "/new:/multiple"
PrependPath TESTPATH /and /new /foo /and
Assert "/foo:/and:/new:/multiple"

TESTPATH=":/foo:/bar:"
PrependPath TESTPATH /bar
Assert ":/foo:/bar:"

TESTPATH="/foo:/bar:"
PrependPath TESTPATH ""
Assert "/foo:/bar:"

TESTPATH=":/foo:/bar"
PrependPath TESTPATH ""
Assert ":/foo:/bar"

TESTPATH="/foo::/bar"
PrependPath TESTPATH ""
Assert "/foo::/bar"

TESTPATH=$'/te st\nnew/foo:/and:/ano\nth er:/more:/te st\nnew/foo:'
PrependPath TESTPATH /ano
Assert $'/ano:/te st\nnew/foo:/and:/ano\nth er:/more:/te st\nnew/foo:'
PrependPath TESTPATH $'/te st\nnew/foo'
Assert $'/ano:/te st\nnew/foo:/and:/ano\nth er:/more:/te st\nnew/foo:'
PrependPath TESTPATH $'/ne w\ner\n'
Assert $'/ne w\ner\n:/ano:/te st\nnew/foo:/and:/ano\nth er:/more:/te st\nnew/foo:'

TESTPATH="/foo:/bar"
PrependPath TESTPATH "/b.r" "/fo+" "/fo*" "/b[a]r" "\foo" "/food?"
Assert "/food?:\foo:/b[a]r:/fo*:/fo+:/b.r:/foo:/bar"

TESTPATH=$':/te[ st\nnew/foo:/a[d:/ano\nth er:/mor(e:'
PrependPath TESTPATH "/a[d" "/mor(e" "/other{" "/ano"
Assert $'/ano:/other{::/te[ st\nnew/foo:/a[d:/ano\nth er:/mor(e:'

TESTPATH=$'\new'
PrependPath TESTPATH "\new"
Assert $'\\new:\new'

TESTPATH=
PrependPath TESTPATH "/foo"
Assert "/foo"
PrependPath TESTPATH ""
Assert ":/foo"

TESTPATH=$'\n:/foo:/bar:\n'
PrependPath TESTPATH "/bar"
Assert $'\n:/foo:/bar:\n'
PrependPath TESTPATH $'\n'
Assert $'\n:/foo:/bar:\n'
PrependPath TESTPATH "/new"
Assert $'/new:\n:/foo:/bar:\n'
PrependPath TESTPATH ""
Assert $':/new:\n:/foo:/bar:\n'

TESTPATH=$':/foo:/bar:\n\n'
PrependPath TESTPATH "/bar"
Assert $':/foo:/bar:\n\n'
PrependPath TESTPATH $'\n'
Assert $'\n::/foo:/bar:\n\n'
PrependPath TESTPATH $'\n\n'
Assert $'\n::/foo:/bar:\n\n'
PrependPath TESTPATH "/new"
Assert $'/new:\n::/foo:/bar:\n\n'

TESTPATH=
PrependPath TESTPATH ""
Assert ":"
PrependPath TESTPATH ""
Assert ":"
PrependPath TESTPATH /new
Assert "/new:"
PrependPath TESTPATH ""
Assert "/new:"

TESTPATH=":/bin:"
PrependPath TESTPATH ""
Assert ":/bin:"
PrependPath TESTPATH "/foo"
Assert "/foo::/bin:"

TESTPATH="::"
PrependPath TESTPATH ""
Assert "::"
PrependPath TESTPATH "/foo"
Assert "/foo:::"

TESTPATH=":::"
PrependPath TESTPATH ""
Assert ":::"
PrependPath TESTPATH "/foo"
Assert "/foo::::"

ORIGPATH="$PATH"
PATH=
TESTPATH="/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/"
PrependPath TESTPATH /usr/sbin /bin /usr/slash /new /foo /new
Assert "/foo:/new:/usr/slash:/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/"
PATH="$ORIGPATH"

i=0
echo
echo "Test: AppendPath"

TESTPATH="/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/"
AppendPath TESTPATH /usr/sbin
Assert "/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/"
AppendPath TESTPATH /bin
Assert "/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/"
AppendPath TESTPATH /usr/slash
Assert "/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/:/usr/slash"
AppendPath TESTPATH /new
Assert "/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/:/usr/slash:/new"
AppendPath TESTPATH /new
Assert "/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/:/usr/slash:/new"
AppendPath TESTPATH ""
Assert "/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/:/usr/slash:/new:"
AppendPath TESTPATH ""
Assert "/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/:/usr/slash:/new:"
AppendPath TESTPATH "/usr"
Assert "/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/:/usr/slash:/new::/usr"

TESTPATH=
AppendPath TESTPATH /multiple /new
Assert "/multiple:/new"
AppendPath TESTPATH /and /new /foo /and
Assert "/multiple:/new:/and:/foo"

TESTPATH=":/foo:/bar:"
AppendPath TESTPATH /bar
Assert ":/foo:/bar:"

TESTPATH="/foo:/bar:"
AppendPath TESTPATH ""
Assert "/foo:/bar:"

TESTPATH=":/foo:/bar"
AppendPath TESTPATH ""
Assert ":/foo:/bar"

TESTPATH="/foo::/bar"
AppendPath TESTPATH ""
Assert "/foo::/bar"

TESTPATH=$'/te st\nnew/foo:/and:/ano\nth er:/more:/te st\nnew/foo:'
AppendPath TESTPATH /ano
Assert $'/te st\nnew/foo:/and:/ano\nth er:/more:/te st\nnew/foo::/ano'
AppendPath TESTPATH $'/te st\nnew/foo'
Assert $'/te st\nnew/foo:/and:/ano\nth er:/more:/te st\nnew/foo::/ano'
AppendPath TESTPATH $'/ne w\ner\n'
Assert $'/te st\nnew/foo:/and:/ano\nth er:/more:/te st\nnew/foo::/ano:/ne w\ner\n'

TESTPATH="/foo:/bar"
AppendPath TESTPATH "/b.r" "/fo+" "/fo*" "/b[a]r" "/bar" "\foo" "/food?"
Assert "/foo:/bar:/b.r:/fo+:/fo*:/b[a]r:\foo:/food?"

TESTPATH=$':/te[ st\nnew/foo:/a[d:/ano\nth er:/mor(e:'
AppendPath TESTPATH "/a[d" "/mor(e" "/other{" "/ano"
Assert $':/te[ st\nnew/foo:/a[d:/ano\nth er:/mor(e::/other{:/ano'

TESTPATH=$'\new'
AppendPath TESTPATH "\new"
Assert $'\new:\\new'

TESTPATH=
AppendPath TESTPATH "/foo"
Assert "/foo"
AppendPath TESTPATH ""
Assert "/foo:"

TESTPATH=$'\n:/foo:/bar:\n'
AppendPath TESTPATH "/bar"
Assert $'\n:/foo:/bar:\n'
AppendPath TESTPATH $'\n'
Assert $'\n:/foo:/bar:\n'
AppendPath TESTPATH "/new"
Assert $'\n:/foo:/bar:\n:/new'
AppendPath TESTPATH ""
Assert $'\n:/foo:/bar:\n:/new:'

TESTPATH=$':/foo:/bar:\n\n'
AppendPath TESTPATH "/bar"
Assert $':/foo:/bar:\n\n'
AppendPath TESTPATH $'\n'
Assert $':/foo:/bar:\n\n:\n'
AppendPath TESTPATH $'\n\n'
Assert $':/foo:/bar:\n\n:\n'
AppendPath TESTPATH "/new"
Assert $':/foo:/bar:\n\n:\n:/new'

TESTPATH=
AppendPath TESTPATH ""
Assert ":"
AppendPath TESTPATH ""
Assert ":"
AppendPath TESTPATH /new
Assert ":/new"
AppendPath TESTPATH ""
Assert ":/new"

TESTPATH=":/bin:"
AppendPath TESTPATH ""
Assert ":/bin:"
AppendPath TESTPATH "/foo"
Assert ":/bin::/foo"

TESTPATH="::"
AppendPath TESTPATH ""
Assert "::"
AppendPath TESTPATH "/foo"
Assert ":::/foo"

TESTPATH=":::"
AppendPath TESTPATH ""
Assert ":::"
AppendPath TESTPATH "/foo"
Assert "::::/foo"

ORIGPATH="$PATH"
PATH=
TESTPATH="/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/"
AppendPath TESTPATH /usr/sbin /bin /usr/slash /new /foo /new
Assert "/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/slash/:/usr/slash:/new:/foo"
PATH="$ORIGPATH"

i=0
echo
echo "Test: RemovePath"

TESTPATH="/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/sbin/"
RemovePath TESTPATH /usr/sbin
Assert "/usr/bin:/bin:/usr/sbin/tmp:/usr/sbin/"

TESTPATH="/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/sbin/:/foo"
RemovePath TESTPATH /usr/bin /usr/sbin /foo
Assert "/bin:/usr/sbin/tmp:/usr/sbin/"

TESTPATH=$':/te st\nnew/foo:/and:/ano\nth er:/more:'
RemovePath TESTPATH /and
Assert $':/te st\nnew/foo:/ano\nth er:/more:'

TESTPATH=$':/te st\nnew/foo:/and:/ano\nth er:/more:/te st\nnew/foo:'
RemovePath TESTPATH $'/te st\nnew/foo'
Assert $':/and:/ano\nth er:/more:'

TESTPATH=$':/te st\nnew/foo:/and:/ano\nth er:/more:'
RemovePath TESTPATH ""
Assert $'/te st\nnew/foo:/and:/ano\nth er:/more'

TESTPATH=$':/te st\nnew/foo:/and:/ano\nth er:/more:'
RemovePath TESTPATH "/a.d" "\and" "/andy?" $'/te st\nnew/fo+' "/an*"
Assert $':/te st\nnew/foo:/and:/ano\nth er:/more:'

TESTPATH=$':/te[ st\nnew/foo:/a[d:/ano\nth er:/mor(e:'
RemovePath TESTPATH "/a[d" "/mor(e" "/other{" "/ano"
Assert $':/te[ st\nnew/foo:/ano\nth er:'

TESTPATH=$':/te st\nnew/foo:/and:/ano\nth er:\n:/more:'
RemovePath TESTPATH "/sp ace" $'/new\nline' $'\n' "" $'/ano\nth er'
Assert $'/te st\nnew/foo:/and:/more'

TESTPATH="\no:\newline"
RemovePath TESTPATH $'\no'
Assert "\no:\newline"
RemovePath TESTPATH "\no"
Assert "\newline"

TESTPATH=$'\n:/more:\n\n:/of:\n:/th is:\n\n:\n'
RemovePath TESTPATH ""
Assert $'\n:/more:\n\n:/of:\n:/th is:\n\n:\n'
RemovePath TESTPATH "\n"
Assert $'\n:/more:\n\n:/of:\n:/th is:\n\n:\n'
RemovePath TESTPATH $'\n\n'
Assert $'\n:/more:/of:\n:/th is:\n'
RemovePath TESTPATH $'\n'
Assert $'/more:/of:/th is'

TESTPATH=$'\n:/more:\n\n:/of:\n:/th is:\n\n:\n'
RemovePath TESTPATH $'\n'
Assert $'/more:\n\n:/of:/th is:\n\n'
RemovePath TESTPATH /of
Assert $'/more:\n\n:/th is:\n\n'

TESTPATH=$'\n:/more:\n\n:/of:\n:/th is::\n'
RemovePath TESTPATH $'\n'
Assert $'/more:\n\n:/of:/th is:'

TESTPATH=$'\n:/more:\n\n:/of:\n:/th is::\n'
RemovePath TESTPATH ""
Assert $'\n:/more:\n\n:/of:\n:/th is:\n'

TESTPATH=":::"
RemovePath TESTPATH "/foo"
Assert ":::"
RemovePath TESTPATH ""
Assert ""

TESTPATH="::"
RemovePath TESTPATH "/foo"
Assert "::"
RemovePath TESTPATH ""
Assert ""

TESTPATH=":"
RemovePath TESTPATH "/foo"
Assert ":"
RemovePath TESTPATH ""
Assert ""

TESTPATH="::/foo"
RemovePath TESTPATH "/foo"
Assert ":"

TESTPATH="::/foo"
RemovePath TESTPATH ""
Assert "/foo"

TESTPATH=":/foo"
RemovePath TESTPATH "/foo"
Assert ":"

TESTPATH=":/foo"
RemovePath TESTPATH ""
Assert "/foo"
RemovePath TESTPATH "/foo"
Assert ""

TESTPATH="/foo::"
RemovePath TESTPATH "/foo"
Assert ":"

TESTPATH="/foo::"
RemovePath TESTPATH ""
Assert "/foo"

TESTPATH="/foo:"
RemovePath TESTPATH "/foo"
Assert ":"

TESTPATH="/foo:"
RemovePath TESTPATH ""
Assert "/foo"

TESTPATH=""
RemovePath TESTPATH ""
Assert ""
RemovePath TESTPATH "/foo"
Assert ""

ORIGPATH="$PATH"
PATH=
TESTPATH="/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/sbin/"
RemovePath TESTPATH /usr/sbin /bin /foo
Assert "/usr/bin:/usr/sbin/tmp:/usr/sbin/"
PATH="$ORIGPATH"

i=0
echo
echo "Test: UniquePath"

TESTPATH="/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/sbin/"
UniquePath TESTPATH
Assert "/usr/bin:/usr/sbin:/bin:/usr/sbin/tmp:/usr/sbin/"
UniquePath TESTPATH
Assert "/usr/bin:/usr/sbin:/bin:/usr/sbin/tmp:/usr/sbin/"

TESTPATH="/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/sbin/:/usr/sbin:/bin"
UniquePath TESTPATH
Assert "/usr/bin:/usr/sbin:/bin:/usr/sbin/tmp:/usr/sbin/"

TESTPATH=":/foo:/bar:/foo"
UniquePath TESTPATH
Assert ":/foo:/bar"

TESTPATH="/foo:/bar:/foo:"
UniquePath TESTPATH
Assert "/foo:/bar:"

TESTPATH=":/foo:/bar:/foo:"
UniquePath TESTPATH
Assert ":/foo:/bar"

TESTPATH="/foo:/bar::/foo"
UniquePath TESTPATH
Assert "/foo:/bar:"

TESTPATH="/foo:/bar::/foo:"
UniquePath TESTPATH
Assert "/foo:/bar:"

TESTPATH=$':/te st\nnew/foo:/ano:/ano\nth er:th er::/more:/te st\nnew/foo:'
UniquePath TESTPATH
Assert $':/te st\nnew/foo:/ano:/ano\nth er:th er:/more'

TESTPATH=$'/te st\nnew/foo:/and:/ano\nth er:/more:/a.d:\\and:/andy?:/te st\nnew/fo+:/an*:/more'
UniquePath TESTPATH
Assert $'/te st\nnew/foo:/and:/ano\nth er:/more:/a.d:\\and:/andy?:/te st\nnew/fo+:/an*'

TESTPATH=$':/te[ st\nnew/foo:/a[d:/ano\nth er:/mor(e:/mor(e:/other{:/ano:/a[d'
UniquePath TESTPATH
Assert $':/te[ st\nnew/foo:/a[d:/ano\nth er:/mor(e:/other{:/ano'

TESTPATH=$'\\no:\newline:\no:\\newline:\no'
UniquePath TESTPATH
Assert $'\\no:\newline:\no:\\newline'

TESTPATH="/foo:/foo"
UniquePath TESTPATH
Assert "/foo"
UniquePath TESTPATH
Assert "/foo"

TESTPATH=":"
UniquePath TESTPATH
Assert ":"

TESTPATH="::"
UniquePath TESTPATH
Assert ":"

TESTPATH=":::"
UniquePath TESTPATH
Assert ":"

TESTPATH=$'\n:/more::/of:\n:/th is:\n\n:\n'
UniquePath TESTPATH
Assert $'\n:/more::/of:/th is:\n\n'

TESTPATH=$':/more:/of::\n\n::/th is:\n\n:\n'
UniquePath TESTPATH
Assert $':/more:/of:\n\n:/th is:\n'

TESTPATH=$'/foo:\n:/foo'
UniquePath TESTPATH
Assert $'/foo:\n'

TESTPATH=$'/foo::'
UniquePath TESTPATH
Assert $'/foo:'

TESTPATH=
UniquePath TESTPATH
Assert ""

ORIGPATH="$PATH"
PATH=
TESTPATH="/usr/bin:/usr/sbin:/bin:/usr/sbin:/usr/sbin/tmp:/usr/sbin/:/bin"
UniquePath TESTPATH
Assert "/usr/bin:/usr/sbin:/bin:/usr/sbin/tmp:/usr/sbin/"
PATH="$ORIGPATH"

Если ваши функции проходят каждый из этих тестов, и никто другой не может найти недостаток, который я не проверял в сценарии, то вы полностью ответили мой вопрос.

Естественно, ни один из ответов, которые я пытался ответить на любые другие связанные вопросы по переполнению стека, не дал мне решения этой проблемы ( 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ). Возможно, я пропустил одну, но, поскольку я ищу все четыре функции на уровне общности, который я указал, это все равно не полностью отвечает на мой вопрос.

То, что я пробовал

Спецификация переменной для изменения с использованием строки легко решается с помощью косвенного расширения, например, если $1 равно PATH, тогда ${!1} равно $PATH. Косвенная установка / экспорт переменной может быть выполнена с помощью export "${1}"=XXX. В конечном решении могут быть более эффективные / элегантные способы работы с несколькими входами, но в качестве запасного решения вы всегда можете просто использовать for-l oop над ними, поэтому задачу можно свести к простому требованию четырех функций, которые выполнить необходимые операции над переменной PATH с одним аргументом.

Не загрязнять родительское пространство имен можно, используя ключевое слово local для переменных, но подфункции в лучшем случае будут unset -f, что не действительно не удовлетворяет требованию не изменять родительскую среду. Присоединение к переменной PATH может быть сделано аккуратно с использованием альтернативного расширения параметра, т.е. что-то вроде PATH="${PATH:+${PATH}:}/new/path". В этом случае предварительное добавление выглядит примерно так: PATH="/new/path${PATH:+:${PATH}}".

. Эти выражения правильно работают с добавлением разделителя двоеточия или без почти все время, а также для особых случаев, когда они не являются простыми условными выражениями. вероятно, может быть добавлено, чтобы исправить это соответствующим образом. Имея все эти строительные блоки в моем распоряжении, суть моей проблемы заключается в том, как надежно разделить путь пуленепробиваемым способом, чтобы иметь возможность проверить, существует ли уже определенный путь. Одна из моих попыток состояла в том, чтобы просто искать строку без извлечения компонентов:

function AppendPath() { echo "$PATH" | grep -Eq "(^|:)$1($|:)" || export PATH="$1${PATH:+:${PATH}}"; }

, но это не удалось, поскольку содержимое $1 интерпретируется как регулярное выражение. Кроме того, вызов grep завершается неудачно, когда PATH пуст, а это означает, что потребуется абсолютный путь, такой как /bin/grep, но он не выглядит особенно переносимым. Для RemovePath я попытался использовать нулевые символы в качестве разделителей и сделать инвертированный grep, например что-то вроде:

function RemovePath() { __path="$(echo -n "$PATH" | tr ':' '\0' | grep -zxvF "$1" | tr '\0' ':')"; export PATH="${__path%:}"; }

Я использовал -F на grep, чтобы убедиться, что он не обрабатывается как регулярное выражение сопоставляло целые строки с -x, использовало нулевые разделители с -z и инвертировало совпадение с -v. Помимо очевидных проблем с определенными угловыми случаями и пустым PATH случаем, grep -zF все еще делит шаблоны поиска на каждой новой строке, несмотря на использование нулевого разделителя, что означает, что попытка удалить $'/foo\nbar' вместо этого просто удалит все экземпляры /foo и bar. Здесь также всплывает хитрая проблема с подстановкой команд $(), в результате чего она усекает все завершающие символы новой строки. Попробуйте:

echo -n "$(echo -n $'Hey\nthere\n\n\n')"

Я пытался использовать IFS=:, чтобы разбить PATH на массив, но, среди прочего, подстановка команд - это то место, где я снова застреваю при попытке собрать его с чем-то вроде "$(IFS=:; echo -n "${pathparts[*]}")" , Сейчас я изучаю встроенную идею bash, основанную на команде read и ручном цикле, чтобы избежать проблем с подстановкой команд, grep и пустых PATH. Я на самом деле хотел избежать загрязнения моего вопроса множеством вещей, которые не работают, но он был запрошен, и я надеюсь, что, по крайней мере, теперь считается, что я сам дал ему серьезное go до публикации.

1 Ответ

0 голосов
/ 15 апреля 2020

Вот решение, которое в рамках моего тестирования всегда будет делать правильно в любой ситуации при управлении PATH -подобными переменными:

# Example: PrependPath PATH /usr/local/cuda/bin
function PrependPath() {
    local __args __item
    for __args in "${@:2}"; do
        [[ -n "${!1}" ]] && while IFS= read -r -d ':' __item; do
            [[ "$__item" == "$__args" ]] && continue 2
        done <<< "${!1}:"
        ([[ -n "${!1#:}" ]] || [[ -z "$__args" ]]) && __args+=':'
        export "${1}"="$__args${!1}"
    done
}
# Example: AppendPath PATH /usr/local/cuda/bin
function AppendPath() {
    local __args __item
    for __args in "${@:2}"; do
        [[ -n "${!1}" ]] && while IFS= read -r -d ':' __item; do
            [[ "$__item" == "$__args" ]] && continue 2
        done <<< "${!1}:"
        ([[ -n "${!1#:}" ]] || [[ -z "$__args" ]]) && __args=":$__args"
        export "${1}"="${!1}$__args"
    done
}
# Example: RemovePath INFOPATH /usr/local/texlive/2019/texmf-dist/doc/info
function RemovePath() {
    local __item __args __path=
    [[ -n "${!1}" ]] && while IFS= read -r -d ':' __item; do
        for __args in "${@:2}"; do
            [[ "$__item" == "$__args" ]] && continue 2
        done
        __path="$__path:$__item"
    done <<< "${!1}:"
    [[ "$__path" != ":" ]] && __path="${__path#:}"
    export "${1}"="$__path"
}
# Example: UniquePath LD_LIBRARY_PATH
function UniquePath() {
    local __item __args __path= __seen=()
    [[ -n "${!1}" ]] && while IFS= read -r -d ':' __item; do
        for __args in "${__seen[@]}"; do
            [[ "$__item" == "$__args" ]] && continue 2
        done
        __seen+=("$__item")
        __path="$__path:$__item"
    done <<< "${!1}:"
    [[ "$__path" != ":" ]] && __path="${__path#:}"
    export "${1}"="$__path"
}

Решением проблемы стало использование read с : в качестве разделителя и использование только циклов bash для проверки / сборки требуемого пути вывода без подстановки команд, grep, awk, sed или любые другие внешние команды.

Свернуты в «однострочники», чтобы их можно было легко использовать в сценариях, в командной строке и в .bashrc:

# Functions to manage arbitrary PATH-like variables
function PrependPath() { local __args __item; for __args in "${@:2}"; do [[ -n "${!1}" ]] && while IFS= read -r -d ':' __item; do [[ "$__item" == "$__args" ]] && continue 2; done <<< "${!1}:"; ([[ -n "${!1#:}" ]] || [[ -z "$__args" ]]) && __args+=':'; export "${1}"="$__args${!1}"; done; }
function AppendPath() { local __args __item; for __args in "${@:2}"; do [[ -n "${!1}" ]] && while IFS= read -r -d ':' __item; do [[ "$__item" == "$__args" ]] && continue 2; done <<< "${!1}:"; ([[ -n "${!1#:}" ]] || [[ -z "$__args" ]]) && __args=":$__args"; export "${1}"="${!1}$__args"; done; }
function RemovePath() { local __item __args __path=; [[ -n "${!1}" ]] && while IFS= read -r -d ':' __item; do for __args in "${@:2}"; do [[ "$__item" == "$__args" ]] && continue 2; done; __path="$__path:$__item"; done <<< "${!1}:"; [[ "$__path" != ":" ]] && __path="${__path#:}"; export "${1}"="$__path"; }
function UniquePath() { local __item __args __path= __seen=(); [[ -n "${!1}" ]] && while IFS= read -r -d ':' __item; do for __args in "${__seen[@]}"; do [[ "$__item" == "$__args" ]] && continue 2; done; __seen+=("$__item"); __path="$__path:$__item"; done <<< "${!1}:"; [[ "$__path" != ":" ]] && __path="${__path#:}"; export "${1}"="$__path"; }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...