Я пытаюсь написать функцию оболочки bash, которая позволит мне удалять дубликаты копий каталогов из моей переменной среды PATH.
Мне сказали, что это можно сделать с помощью одной строки, используя awk
команду, но я не могу понять, как это сделать. Кто-нибудь знает как?
Ответы:
Если у вас еще нет дубликатов в
PATH
и вы хотите добавлять каталоги только в том случае, если их еще нет, вы можете легко сделать это с помощью одной оболочки.А вот фрагмент оболочки, из которого удаляются дубликаты
$PATH
. Он просматривает записи одну за другой и копирует те, которые еще не видели.источник
PATH=$PATH:x=b
x в исходном PATH может иметь значение a, поэтому при повторении по порядку новое значение будет игнорироваться, а при обратном порядке новое значение вступит в силу.PATH=x:$PATH
.PATH=$PATH:...
нетPATH=...:$PATH
. Таким образом, правильнее повторять в обратном порядке. Даже если ваш путь тоже сработает, люди присоединяются обратным путем.Вот понятное однострочное решение, которое делает все правильно: удаляет дубликаты, сохраняет порядок путей и не добавляет двоеточие в конце. Поэтому он должен дать вам дедуплицированную переменную PATH, которая будет вести себя точно так же, как и оригинал:
Он просто разделяется на двоеточие (
split(/:/, $ENV{PATH})
), использует использованиеgrep { not $seen{$_}++ }
для фильтрации любых повторяющихся экземпляров путей, за исключением первого вхождения, а затем объединяет оставшиеся вместе, разделенные двоеточиями, и печатает результат (print join(":", ...)
).Если вам нужна дополнительная структура, а также возможность дедупликации других переменных, попробуйте этот фрагмент, который я сейчас использую в своей конфигурации:
Этот код дедуплицирует как PATH, так и MANPATH, и вы можете легко вызывать
dedup_pathvar
другие переменные, которые содержат разделенные двоеточиями списки путей (например, PYTHONPATH).источник
chomp
чтобы удалить завершающий перевод строки. Это сработало для меня:perl -ne 'chomp; print join(":", grep { !$seen{$_}++ } split(/:/))' <<<"$PATH"
Вот гладкий:
Дольше (чтобы увидеть, как это работает):
Хорошо, так как вы новичок в Linux, вот как на самом деле установить PATH без завершающего ":"
Кстати, убедитесь, что в вашей переменной PATH нет каталогов, содержащих «:», иначе это может привести к путанице.
некоторые кредиты:
источник
echo -n
. Кажется, что ваши команды не работают с «здесь строками», например try:awk -v RS=: -v ORS=: '!arr[$0]++' <<< ".:/foo/bin:/bar/bin:/foo/bin"
Вот один лайнер AWK.
где:
printf %s "$PATH"
печатает содержимое$PATH
без завершающей строкиRS=:
изменяет символ разделителя входной записи (по умолчанию - новая строка)ORS=
изменяет разделитель выходной записи на пустую строкуa
имя неявно созданного массива$0
ссылается на текущую записьa[$0]
является ассоциативным массивом разыменования++
это оператор постинкрементного!a[$0]++
защищает правую сторону, то есть он гарантирует, что текущая запись будет напечатана, только если она не была напечатана ранееNR
номер текущей записи, начиная с 1Это означает, что AWK используется для разделения
PATH
содержимого по:
символам разделителя и для фильтрации повторяющихся записей без изменения порядка.Поскольку ассоциативные массивы AWK реализованы в виде хеш-таблиц, время выполнения является линейным (т. Е. В O (n)).
Обратите внимание, что нам не нужно искать
:
символы в кавычках, потому что оболочки не предоставляют кавычки для поддержки каталогов с:
именем вPATH
переменной.Awk + паста
Вышесказанное можно упростить с помощью пасты:
Команда
paste
используется для разбивки выходных данных awk двоеточиями. Это упрощает действие awk для печати (действие по умолчанию).питон
Так же, как Python двухлинейный:
источник
paste
не работает для меня, если я не добавлю трейлинг-
для использования STDIN.-v
или я получаю сообщение об ошибке.-v RS=: -v ORS=
, Просто разные вкусыawk
синтаксиса.Там было подобное обсуждение по этому поводу здесь .
Я придерживаюсь немного другого подхода. Вместо того, чтобы просто принимать PATH, установленный из всех устанавливаемых файлов инициализации, я предпочитаю
getconf
сначала указывать системный путь и размещать его, затем добавить предпочтительный порядок путей, а затем использоватьawk
для удаления любых дубликатов. Это может или не может действительно ускорить выполнение команд (и в теории быть более безопасным), но это дает мне теплые размышления.источник
:
кPATH
(т.е. пустой строки ввода), потому что тогда текущий рабочий каталог является частью вашейPATH
.Пока мы добавляем не-awk oneliners:
(Может быть так просто,
PATH=$(zsh -fc 'typeset -U path; echo $PATH')
но zsh всегда читает хотя бы одинzshenv
файл конфигурации, который можно изменитьPATH
.)Он использует две приятные функции Zsh:
typeset -T
)typeset -U
).источник
Это использует Perl и имеет несколько преимуществ:
/usr/bin:/sbin:/usr/bin
приведет к/usr/bin:/sbin
)источник
Также
sed
(здесь используетсяsed
синтаксис GNU ) можно выполнить работу:этот работает хорошо только в том случае, если первый путь
.
похож на пример с кланом.В общем случае вам нужно добавить еще одну
s
команду:Работает даже на такой конструкции:
источник
Как продемонстрировали другие, в одной строке можно использовать awk, sed, perl, zsh или bash, в зависимости от вашего допуска к длинным строкам и читабельности. Вот функция bash, которая
функция Баш
использование
Удалить пупы из PATH
источник
Это моя версия:
Использование:
path_no_dup "$PATH"
Образец вывода:
источник
В последних версиях bash (> = 4) также есть ассоциативные массивы, то есть вы можете использовать для этого bash 'one liner':
где:
IFS
изменяет разделитель поля ввода на:
declare -A
объявляет ассоциативный массив${a[$i]+_}
является расширением параметра, означающим:_
подставляется тогда и только тогда, когдаa[$i]
установлено. Это похоже на то,${parameter:+word}
что также проверяет на ненулевое значение. Таким образом, в следующей оценке условия выражение_
(то есть строка из одного символа) оценивается как истинное (это эквивалентно-n _
), а пустое выражение оценивается как ложное.источник
${a[$i]+_}
отредактировав свой ответ и добавив одну маркировку? Остальное совершенно понятно, но ты потерял меня там. Спасибо.Объяснение кода awk:
Помимо краткости, этот однострочный быстрый: awk использует цепочку хеш-таблиц для достижения амортизированной производительности O (1).
на основе удаления повторяющихся записей $ PATH
источник
if ( !x[$i]++ )
. Благодарю.Используйте
awk
для разделения пути:
, затем зациклите каждое поле и сохраните его в массиве. Если вы встретите поле, которое уже находится в массиве, это означает, что вы видели его раньше, поэтому не печатайте его.Вот пример:
(Обновлено, чтобы удалить трейлинг
:
.)источник
Решение - не такое элегантное, как те, которые изменяют переменные * RS, но, возможно, достаточно ясное:
Вся программа работает в блоках BEGIN и END . Он извлекает переменную PATH из среды, разделяя ее на единицы. Затем он выполняет итерацию по результирующему массиву p (который создается по порядку
split()
). Массив e является ассоциативным массивом, который используется для определения того, видели ли мы текущий элемент пути (например, / usr / local / bin ) и, если нет, добавляется к np , с логикой для добавления двоеточия к нп, если в нп уже есть текст . Блок END просто повторяет np . Это может быть еще более упрощено путем добавления-F:
флаг, исключая третий аргументsplit()
(по умолчанию это FS ), и изменяясьnp = np ":"
наnp = np FS
, давая нам:Наивно, я полагал, что
for(element in array)
это сохранит порядок, но это не так, поэтому мое оригинальное решение не сработает, так как люди расстроятся, если кто-то вдруг нарушит порядок их$PATH
:источник
Сохраняется только первое вхождение, а относительный порядок поддерживается.
источник
Я бы сделал это только с помощью основных инструментов, таких как tr, sort и uniq:
Если на вашем пути нет ничего особенного или странного, это должно сработать
источник
sort -u
вместоsort | uniq
.