Я ищу четыре простые пуленепробиваемые , максимально короткие 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 до публикации.