Как я могу манипулировать элементами $PATH в сценариях оболочки?
Есть ли идиоматический способ удаления элементов из PATH-подобных переменных оболочки?
То есть я хочу взять
PATH=/home/joe/bin:/usr/local/bin:/usr/bin:/bin:/path/to/app/bin:.
и удалите или замените /path/to/app/bin
, не сжимая остальную часть переменной. Дополнительные баллы за то, что позволили мне ввести новые элементы в произвольные позиции. Цель будет распознана четко определенной строкой и может появиться в любой точке списка.
Я знаю, что видел это, и, возможно, могу что-то совладать сам по себе, но я ищу хороший подход. Переносимость и стандартизация плюс.
Я использую bash, но пример приветствуется и в вашей любимой оболочке.
Контекст здесь заключается в необходимости удобного переключения между несколькими версиями (один для проведения анализа, другой для работы с каркасом) большого пакета научного анализа, который производит пару десятков исполняемых файлов, имеет данные, спрятанные вокруг файловой системы, и использует переменную окружения, чтобы помочь найти все это. Я хотел бы написать script, который выбирает версию, и должен иметь возможность удалить элементы $PATH
, относящиеся к текущей активной версии, и заменить их теми же элементами, что и для новой версии.
Это связано с проблемой предотвращения повторных элементов $PATH
при повторном запуске сценариев входа и т.д.
Ответы
Ответ 1
Решение предлагаемого решения от dmckee:
- Хотя некоторые версии Bash могут допускать дефисы в именах функций, другие (MacOS X) этого не делают.
- Я не вижу необходимости использовать возврат непосредственно перед завершением функции.
- Я не вижу необходимости в всех полуколонах.
- Я не понимаю, почему вы экспортируете значение path-element-by-pattern. Подумайте о
export
как эквиваленте установки (или даже создания) глобальной переменной - чего-то следует избегать, когда это возможно.
- Я не уверен, чего вы ожидаете от "
replace-path PATH $PATH /usr
", но он не делает того, чего я ожидаю.
Рассмотрим значение PATH, которое начинается с:
.
/Users/jleffler/bin
/usr/local/postgresql/bin
/usr/local/mysql/bin
/Users/jleffler/perl/v5.10.0/bin
/usr/local/bin
/usr/bin
/bin
/sw/bin
/usr/sbin
/sbin
Результат, полученный мной (от 'replace-path PATH $PATH /usr
'):
.
/Users/jleffler/bin
/local/postgresql/bin
/local/mysql/bin
/Users/jleffler/perl/v5.10.0/bin
/local/bin
/bin
/bin
/sw/bin
/sbin
/sbin
Я бы ожидал получить свой первоначальный путь назад, поскольку /usr не отображается как элемент (полный), только как часть элемента пути.
Это можно зафиксировать в replace-path
, изменив одну из команд sed
:
export $path=$(echo -n $list | tr ":" "\n" | sed "s:^$removestr\$:$replacestr:" |
tr "\n" ":" | sed "s|::|:|g")
Я использовал ':' вместо '|' для разделения частей замены, так как '|' могут (теоретически) появляться в компоненте пути, тогда как по определению PATH двоеточие не может. Я заметил, что второй sed
может удалить текущий каталог из середины PATH. То есть, допустимое (хотя и извращенное) значение PATH может быть:
PATH=/bin::/usr/local/bin
После обработки текущий каталог больше не будет находиться в PATH.
Аналогичное изменение привязки соответствия подходит для path-element-by-pattern
:
export $target=$(echo -n $list | tr ":" "\n" | grep -m 1 "^$pat\$")
Я отмечаю попутно, что grep -m 1
не является стандартным (это расширение GNU, также доступное на MacOS X). И действительно, опция -n
для echo
также нестандартна; вам было бы лучше просто удалить конечный двоеточие, добавленный благодаря преобразованию новой строки из эха в двоеточие. Поскольку path-element-by-pattern используется только один раз, имеет нежелательные побочные эффекты (он сжимает любую ранее существовавшую экспортированную переменную под названием $removestr
), ее можно разумно заменить ее телом. Это, наряду с более либеральным использованием котировок, чтобы избежать проблем с пространством или нежелательным расширением имени файла, приводит к:
# path_tools.bash
#
# A set of tools for manipulating ":" separated lists like the
# canonical $PATH variable.
#
# /bin/sh compatibility can probably be regained by replacing $( )
# style command expansion with ` ` style
###############################################################################
# Usage:
#
# To remove a path:
# replace_path PATH $PATH /exact/path/to/remove
# replace_path_pattern PATH $PATH <grep pattern for target path>
#
# To replace a path:
# replace_path PATH $PATH /exact/path/to/remove /replacement/path
# replace_path_pattern PATH $PATH <target pattern> /replacement/path
#
###############################################################################
# Remove or replace an element of $1
#
# $1 name of the shell variable to set (e.g. PATH)
# $2 a ":" delimited list to work from (e.g. $PATH)
# $3 the precise string to be removed/replaced
# $4 the replacement string (use "" for removal)
function replace_path () {
path=$1
list=$2
remove=$3
replace=$4 # Allowed to be empty or unset
export $path=$(echo "$list" | tr ":" "\n" | sed "s:^$remove\$:$replace:" |
tr "\n" ":" | sed 's|:$||')
}
# Remove or replace an element of $1
#
# $1 name of the shell variable to set (e.g. PATH)
# $2 a ":" delimited list to work from (e.g. $PATH)
# $3 a grep pattern identifying the element to be removed/replaced
# $4 the replacement string (use "" for removal)
function replace_path_pattern () {
path=$1
list=$2
removepat=$3
replacestr=$4 # Allowed to be empty or unset
removestr=$(echo "$list" | tr ":" "\n" | grep -m 1 "^$removepat\$")
replace_path "$path" "$list" "$removestr" "$replacestr"
}
У меня есть Perl script, называемый echopath
, который я считаю полезным при отладке проблем с PATH-подобными переменными:
#!/usr/bin/perl -w
#
# "@(#)$Id: echopath.pl,v 1.7 1998/09/15 03:16:36 jleffler Exp $"
#
# Print the components of a PATH variable one per line.
# If there are no colons in the arguments, assume that they are
# the names of environment variables.
@ARGV = $ENV{PATH} unless @ARGV;
foreach $arg (@ARGV)
{
$var = $arg;
$var = $ENV{$arg} if $arg =~ /^[A-Za-z_][A-Za-z_0-9]*$/;
$var = $arg unless $var;
@lst = split /:/, $var;
foreach $val (@lst)
{
print "$val\n";
}
}
Когда я запускаю измененное решение на тестовом коде ниже:
echo
xpath=$PATH
replace_path xpath $xpath /usr
echopath $xpath
echo
xpath=$PATH
replace_path_pattern xpath $xpath /usr/bin /work/bin
echopath xpath
echo
xpath=$PATH
replace_path_pattern xpath $xpath "/usr/.*/bin" /work/bin
echopath xpath
Вывод:
.
/Users/jleffler/bin
/usr/local/postgresql/bin
/usr/local/mysql/bin
/Users/jleffler/perl/v5.10.0/bin
/usr/local/bin
/usr/bin
/bin
/sw/bin
/usr/sbin
/sbin
.
/Users/jleffler/bin
/usr/local/postgresql/bin
/usr/local/mysql/bin
/Users/jleffler/perl/v5.10.0/bin
/usr/local/bin
/work/bin
/bin
/sw/bin
/usr/sbin
/sbin
.
/Users/jleffler/bin
/work/bin
/usr/local/mysql/bin
/Users/jleffler/perl/v5.10.0/bin
/usr/local/bin
/usr/bin
/bin
/sw/bin
/usr/sbin
/sbin
Это выглядит правильно для меня - по крайней мере, для моего определения проблемы.
Замечу, что echopath LD_LIBRARY_PATH
оценивает $LD_LIBRARY_PATH
. Было бы неплохо, если бы ваши функции могли это сделать, поэтому пользователь мог бы ввести:
replace_path PATH /usr/bin /work/bin
Это можно сделать, используя:
list=$(eval echo '$'$path)
Это приводит к пересмотру кода:
# path_tools.bash
#
# A set of tools for manipulating ":" separated lists like the
# canonical $PATH variable.
#
# /bin/sh compatibility can probably be regained by replacing $( )
# style command expansion with ` ` style
###############################################################################
# Usage:
#
# To remove a path:
# replace_path PATH /exact/path/to/remove
# replace_path_pattern PATH <grep pattern for target path>
#
# To replace a path:
# replace_path PATH /exact/path/to/remove /replacement/path
# replace_path_pattern PATH <target pattern> /replacement/path
#
###############################################################################
# Remove or replace an element of $1
#
# $1 name of the shell variable to set (e.g. PATH)
# $2 the precise string to be removed/replaced
# $3 the replacement string (use "" for removal)
function replace_path () {
path=$1
list=$(eval echo '$'$path)
remove=$2
replace=$3 # Allowed to be empty or unset
export $path=$(echo "$list" | tr ":" "\n" | sed "s:^$remove\$:$replace:" |
tr "\n" ":" | sed 's|:$||')
}
# Remove or replace an element of $1
#
# $1 name of the shell variable to set (e.g. PATH)
# $2 a grep pattern identifying the element to be removed/replaced
# $3 the replacement string (use "" for removal)
function replace_path_pattern () {
path=$1
list=$(eval echo '$'$path)
removepat=$2
replacestr=$3 # Allowed to be empty or unset
removestr=$(echo "$list" | tr ":" "\n" | grep -m 1 "^$removepat\$")
replace_path "$path" "$removestr" "$replacestr"
}
В настоящее время также работает следующий пересмотренный тест:
echo
xpath=$PATH
replace_path xpath /usr
echopath xpath
echo
xpath=$PATH
replace_path_pattern xpath /usr/bin /work/bin
echopath xpath
echo
xpath=$PATH
replace_path_pattern xpath "/usr/.*/bin" /work/bin
echopath xpath
Он производит тот же результат, что и раньше.
Ответ 2
Отправляя мой ответ Какой самый элегантный способ удалить путь из переменной $PATH в Bash?:
#!/bin/bash
IFS=:
# convert it to an array
t=($PATH)
unset IFS
# perform any array operations to remove elements from the array
t=(${t[@]%%*usr*})
IFS=:
# output the new array
echo "${t[*]}"
или однострочный:
PATH=$(IFS=':';t=($PATH);unset IFS;t=(${t[@]%%*usr*});IFS=':';echo "${t[*]}");
Ответ 3
Для удаления элемента вы можете использовать sed:
#!/bin/bash
NEW_PATH=$(echo -n $PATH | tr ":" "\n" | sed "/foo/d" | tr "\n" ":")
export PATH=$NEW_PATH
удалит пути, содержащие "foo" из пути.
Вы также можете использовать sed для вставки новой строки до или после данной строки.
Изменить: вы можете удалять дубликаты путем прокладки через сортировку и uniq:
echo -n $PATH | tr ":" "\n" | sort | uniq -c | sed -n "/ 1 / s/.*1 \(.*\)/\1/p" | sed "/foo/d" | tr "\n" ":"
Ответ 4
В ответах на "есть несколько релевантных программ:" Как избежать дублирования переменной пути в csh". Они больше концентрируются на том, чтобы не было повторяющихся элементов, но script я я могу использовать как:
export PATH=$(clnpath $head_dirs:$PATH:$tail_dirs $remove_dirs)
Предполагая, что у вас есть одна или несколько каталогов в $head_dirs и одна или несколько каталогов в $tail_dirs и одна или несколько каталогов в $remove_dirs, тогда она использует оболочку для объединения головной, текущей и хвостовой частей в массивное значение и затем удаляет каждую из каталогов, перечисленных в $remove_dirs, из результата (а не ошибки, если они не существуют), а также устраняет второе и последующее вхождения любого каталога в пути.
Это не касается размещения компонентов пути в определенной позиции (кроме как в начале или в конце, а только косвенно). Обоснованно указывать, где вы хотите добавить новый элемент или какой элемент, который хотите заменить, является беспорядочным.
Ответ 5
Просто обратите внимание, что bash может выполнять поиск и замену. Он может выполнять все обычные "один раз или все", случаи [в] чувствительных параметрах, которые вы ожидаете.
На странице man:
${параметр/шаблон/строка}
Шаблон расширяется, чтобы создать шаблон так же, как при расширении пути. Параметр расширен, и самое длинное совпадение шаблона с его значением заменяется на строку. Если Ipattern начинается с /, все совпадения шаблона заменяются строкой. Обычно заменяется только первое совпадение. Если шаблон начинается С#, он должен совпадать в начале расширенного значения параметра. Если шаблон начинается с%, он должен совпадать в конце расширенного значения параметра. Если строка имеет значение NULL, совпадения шаблона удаляются, и шаблон/следующий шаблон может быть опущен. Если параметр равен @или
*, операция замещения применяется к каждому позиционному параметру в свою очередь, и расширение является результирующим списком. Если параметр является переменной массива, подстроенной с помощью @или
*, операция замены применяется поочередно к каждому члену массива, а расширение - это результирующий список.
Вы также можете разбить поле, установив $IFS (разделитель полей ввода) на нужный разделитель.
Ответ 6
ОК, спасибо всем респондентам. Я подготовил инкапсулированную версию ответа флорина. Первый проход выглядит следующим образом:
# path_tools.bash
#
# A set of tools for manipulating ":" separated lists like the
# canonical $PATH variable.
#
# /bin/sh compatibility can probably be regained by replacing $( )
# style command expansion with ` ` style
###############################################################################
# Usage:
#
# To remove a path:
# replace-path PATH $PATH /exact/path/to/remove
# replace-path-pattern PATH $PATH <grep pattern for target path>
#
# To replace a path:
# replace-path PATH $PATH /exact/path/to/remove /replacement/path
# replace-path-pattern PATH $PATH <target pattern> /replacement/path
#
###############################################################################
# Finds the _first_ list element matching $2
#
# $1 name of a shell variable to be set
# $2 name of a variable with a path-like structure
# $3 a grep pattern to match the desired element of $1
function path-element-by-pattern (){
target=$1;
list=$2;
pat=$3;
export $target=$(echo -n $list | tr ":" "\n" | grep -m 1 $pat);
return
}
# Removes or replaces an element of $1
#
# $1 name of the shell variable to set (i.e. PATH)
# $2 a ":" delimited list to work from (i.e. $PATH)
# $2 the precise string to be removed/replaced
# $3 the replacement string (use "" for removal)
function replace-path () {
path=$1;
list=$2;
removestr=$3;
replacestr=$4; # Allowed to be ""
export $path=$(echo -n $list | tr ":" "\n" | sed "s|$removestr|$replacestr|" | tr "\n" ":" | sed "s|::|:|g");
unset removestr
return
}
# Removes or replaces an element of $1
#
# $1 name of the shell variable to set (i.e. PATH)
# $2 a ":" delimited list to work from (i.e. $PATH)
# $2 a grep pattern identifying the element to be removed/replaced
# $3 the replacement string (use "" for removal)
function replace-path-pattern () {
path=$1;
list=$2;
removepat=$3;
replacestr=$4; # Allowed to be ""
path-element-by-pattern removestr $list $removepat;
replace-path $path $list $removestr $replacestr;
}
По-прежнему требуется захват ошибок во всех функциях, и я должен, вероятно, придерживаться решения повторного пути, пока я нахожусь в нем.
Вы используете его, выполняя . /include/path/path_tools.bash
в рабочем script и вызывая функции replace-path*
.
Я все еще открыт для новых и/или лучших ответов.
Ответ 7
Это легко использовать awk.
Заменить
{
for(i=1;i<=NF;i++)
if($i == REM)
if(REP)
print REP;
else
continue;
else
print $i;
}
Запустите его, используя
function path_repl {
echo $PATH | awk -F: -f rem.awk REM="$1" REP="$2" | paste -sd:
}
$ echo $PATH
/bin:/usr/bin:/home/js/usr/bin
$ path_repl /bin /baz
/baz:/usr/bin:/home/js/usr/bin
$ path_repl /bin
/usr/bin:/home/js/usr/bin
Append
Вставляет в заданное положение. По умолчанию он добавляется в конце.
{
if(IDX < 1) IDX = NF + IDX + 1
for(i = 1; i <= NF; i++) {
if(IDX == i)
print REP
print $i
}
if(IDX == NF + 1)
print REP
}
Запустите его, используя
function path_app {
echo $PATH | awk -F: -f app.awk REP="$1" IDX="$2" | paste -sd:
}
$ echo $PATH
/bin:/usr/bin:/home/js/usr/bin
$ path_app /baz 0
/bin:/usr/bin:/home/js/usr/bin:/baz
$ path_app /baz -1
/bin:/usr/bin:/baz:/home/js/usr/bin
$ path_app /baz 1
/baz:/bin:/usr/bin:/home/js/usr/bin
Удалить дубликаты
Это сохраняет первые вхождения.
{
for(i = 1; i <= NF; i++) {
if(!used[$i]) {
print $i
used[$i] = 1
}
}
}
Начните так:
echo $PATH | awk -F: -f rem_dup.awk | paste -sd:
Проверить, существуют ли все элементы
Следующее выведет сообщение об ошибке для всех записей, которые не существуют в файловой системе, и возвращает ненулевое значение.
echo -n $PATH | xargs -d: stat -c %n
Чтобы просто проверить, все ли элементы являются путями и получить код возврата, вы также можете использовать test
:
echo -n $PATH | xargs -d: -n1 test -d
Ответ 8
предположим, что
echo $PATH
/usr/lib/jvm/java-1.6.0/bin:lib/jvm/java-1.6.0/bin/:/lib/jvm/java-1.6.0/bin/:/usr/lib/qt-3.3/bin:/usr/lib/ccache:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/tvnadeesh/bin
Если вы хотите удалить /lib/jvm/java -1.6.0/bin/, выполните следующие действия
export PATH=$(echo $PATH | sed 's/\/lib\/jvm\/java-1.6.0\/bin\/://g')
sed
будет принимать входные данные из echo $PATH
и заменить /lib/jvm/java -1.6.0/bin/: пустым
таким образом вы можете удалить
Ответ 9
- Заказ PATH не распространяется.
- Обрабатывает угловые случаи, такие как пустой путь, пространство в пути изящно
- Частичное соответствие dir не дает ложных срабатываний
- Лечит путь по голове и хвосту PATH надлежащим образом. Нет : мусор и т.д.
Скажите, что у вас есть /Foo:/некоторый/путь:/некоторые/путь/dir1:/некоторые/путь/dir2:/бар
и вы хотите заменить /Некоторые/путь
Затем он правильно заменяет "/some/path", но
оставляет "/some/path/dir1" или "/some/path/dir2", как и следовало ожидать.
function __path_add(){
if [ -d "$1" ] ; then
local D=":${PATH}:";
[ "${D/:$1:/:}" == "$D" ] && PATH="$PATH:$1";
PATH="${PATH/#:/}";
export PATH="${PATH/%:/}";
fi
}
function __path_remove(){
local D=":${PATH}:";
[ "${D/:$1:/:}" != "$D" ] && PATH="${D/:$1:/:}";
PATH="${PATH/#:/}";
export PATH="${PATH/%:/}";
}
# Just for the shake of completeness
function __path_replace(){
if [ -d "$2" ] ; then
local D=":${PATH}:";
if [ "${D/:$1:/:}" != "$D" ] ; then
PATH="${D/:$1:/:$2:}";
PATH="${PATH/#:/}";
export PATH="${PATH/%:/}";
fi
fi
}
Похожие сообщения
Какой самый элегантный способ удалить путь из переменной $PATH в Bash?
Ответ 10
Первым делом, чтобы вставить в мою голову только часть строки, является замена sed.
Пример:
если echo $PATH = > "/usr/pkg/bin:/usr/bin:/bin:/usr/pkg/games:/usr/pkg/X11R6/bin"
затем изменить "/usr/bin" на "/usr/local/bin" можно следующим образом:
## выводит стандартный выходной файл
## вместо символа слэша ( "/" ) используется символ "=", так как это будет беспорядочно,
# альтернативный символ цитирования должен быть маловероятным в PATH
## символ разделителя пути ":" удаляется и снова добавляется сюда,
# может потребоваться дополнительный двоеточие после последнего пути
echo $PATH | sed '=/usr/bin: =/usr/local/bin: ='
Это решение заменяет весь элемент пути, поэтому может быть избыточным, если новый элемент аналогичен.
Если новые PATH-s не являются динамическими, но всегда в пределах некоторого постоянного набора, вы можете сохранить их в переменной и назначить по мере необходимости:
PATH = $TEMP_PATH_1;
# команды...;\п
PATH = $TEMP_PATH_2;
# команды и т.д.;
Не может быть того, о чем вы думали. некоторые из соответствующих команд на bash/unix:
Pushd
POPD
CD
ls # может быть l -1A для одного столбца;
найти
Grep
который # может подтвердить, что файл - это то, откуда вы думаете, откуда оно взялось;
окр
Тип
.. и все это и многое другое имеют отношение к PATH или каталогам в целом. Часть изменения текста может быть выполнена любым количеством способов!
Независимо от выбранного решения было бы 4 части:
1) выберите путь, как он
2) декодировать путь, чтобы найти часть, требующую изменений
3) определение того, какие изменения необходимы/интеграция этих изменений
4) валидация/окончательная интеграция/установка переменной
Ответ 11
В соответствии с ответом dj_segfault, я делаю это в сценариях, которые добавляют/дополняют переменные среды, которые могут выполняться несколько раз:
ld_library_path=${ORACLE_HOME}/lib
LD_LIBRARY_PATH=${LD_LIBRARY_PATH//${ld_library_path}?(:)/}
export LD_LIBRARY_PATH=${ld_library_path}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
Использование этой же методики для удаления, замены или управления записями в PATH является тривиальным, учитывая сопоставление шаблонов с расширением имени и поддержкой шаблонов для расширения параметров оболочки.
Ответ 12
Сейчас я предпочитаю использовать ruby, а не awk/sed/foo, так что здесь мой подход к дуракам,
# add it to the path
PATH=~/bin/:$PATH:~/bin
export PATH=$(ruby -e 'puts ENV["PATH"].split(/:/).uniq.join(":")')
создать функцию для повторного использования,
mungepath() {
export PATH=$(ruby -e 'puts ENV["PATH"].split(/:/).uniq.join(":")')
}
Хеш, массивы и строки в рубине одного лайнера :)