Удалите дублирующиеся записи $PATH с командой awk

Препятствовать тому, чтобы текст был разделен между </Document> и следующее <Document> Вам, вероятно, придется использовать серию sed команды (cf. Комментарий жабр выше).

По существу sed читает весь файл в буфер хранения (так, чтобы содержание файла можно было рассматривать как одну строку), и отмечает первое и последнее Document теги для последующей обработки.

# version 1
# marker: HERE
cat file.xml | 
sed -n '1h;1!H;${;g;s/\(<Document>.*<\/Document>\)/HERE\1HERE/g;p;}' | 
sed -n -e '/HERE<Document>/,/<\/Document>HERE/ p' | 
sed -e 's/^ *HERE\(<Document>\)/\1/' -e 's/\(<\/Document>\)HERE *$/\1/'

# version 2    (using the Bash shell)
# marker: $'\001'
cat file.xml | 
sed -n $'1h;1!H;${;g;s/\\(<Document>.*<\\/Document>\\)/\001\\1\001/g;p;}' | 
sed -n -e $'/\001<Document>/,/<\\/Document>\001/ p' | 
sed -e $'s/^ *\001//' -e $'s/\001 *$//' | 
cat -vet

... но я предполагаю, что все это могло быть сделано более изящно (и надежно) использование xmlstarlet!

49
29.05.2017, 01:32
17 ответов

Если у Вас уже нет дубликатов в PATH и Вы только хотите добавить каталоги, если они уже не там, можно сделать это легко с одной только оболочкой.

for x in /path/to/add …; do
  case ":$PATH:" in
    *":$x:"*) :;; # already there
    *) PATH="$x:$PATH";;
  esac
done

И вот отрывок оболочки, который удаляет дубликаты из $PATH. Это проходит записи один за другим и копирует тех, которые еще не были замечены.

if [ -n "$PATH" ]; then
  old_PATH=$PATH:; PATH=
  while [ -n "$old_PATH" ]; do
    x=${old_PATH%%:*}       # the first remaining entry
    case $PATH: in
      *:"$x":*) ;;          # already there
      *) PATH=$PATH:$x;;    # not there yet
    esac
    old_PATH=${old_PATH#*:}
  done
  PATH=${PATH#:}
  unset old_PATH x
fi
38
27.01.2020, 19:33
  • 1
    Было бы лучше, если выполняют итерации объектов в $PATH reversely, потому что более поздние недавно обычно добавляются, и у них могло бы быть актуальное значение. –  Eric Wang 03.09.2016, 09:48
  • 2
    @EricWang я не понимаю Ваше обоснование. Элементы ПУТИ пересечены по всей длине, поэтому когда существуют дубликаты, второй дубликат эффективно проигнорирован. Итерация от наоборот изменила бы порядок. –  Gilles 'SO- stop being evil' 03.09.2016, 13:50
  • 3
    @Gilles при дублировании переменной в ПУТИ, вероятно, она добавляется таким образом: PATH=$PATH:x=b, x в первоначальном тракте мог бы иметь значение a, таким образом когда выполнят итерации в порядке, затем новое значение будет проигнорировано, но когда в обратном порядке, новое значение вступит в силу. –  Eric Wang 03.09.2016, 17:38
  • 4
    @EricWang В этом случае, добавленная стоимость не имеет никакого эффекта, так должен быть проигнорирован. Путем движения назад, Вы заставляете добавленную стоимость прибыть прежде. Если бы добавленная стоимость, как предполагалось, пошла прежде, то она была бы добавлена как PATH=x:$PATH. –  Gilles 'SO- stop being evil' 03.09.2016, 18:42
  • 5
    @Gilles при добавлении чего-то которое означает, что это еще не там, или Вы хотите переопределить старое значение, таким образом, необходимо сделать новую добавленную переменную видимой. И, условно, обычно это, добавляют таким образом: PATH=$PATH:... нет PATH=...:$PATH. Таким образом более следует выполнить итерации инвертированного порядка. Даже при том, что Вы, путь также работал бы, затем люди, добавляете в пути обратный путь. –  Eric Wang 03.09.2016, 19:13

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

Вот пример:

$ MYPATH=.:/foo/bar/bin:/usr/bin:/foo/bar/bin
$ awk -F: '{for(i=1;i<=NF;i++) if(!($i in arr)){arr[$i];printf s$i;s=":"}}' <<< "$MYPATH"
.:/foo/bar/bin:/usr/bin

(Обновленный для удаления запаздывания :.)

0
27.01.2020, 19:33

Была подобная дискуссия об этом здесь.

Я проявляю определенный другой подход. Вместо того, чтобы просто принять ПУТЬ, который установлен от всех различных файлов инициализации, которые установлены, я предпочитаю использовать getconf чтобы определить системный путь и поместить его сначала, затем добавьте мой предпочтительный порядок пути, затем используйте awk удалить любые дубликаты. Это может или не может действительно ускорить выполнение команды (и в теории быть более безопасным), но это дает мне теплый fuzzies.

# I am entering my preferred PATH order here because it gets set,
# appended, reset, appended again and ends up in such a jumbled order.
# The duplicates get removed, preserving my preferred order.
#
PATH=$(command -p getconf PATH):/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
# Remove duplicates
PATH="$(printf "%s" "${PATH}" | /usr/bin/awk -v RS=: -v ORS=: '!($0 in a) {a[$0]; print}')"
export PATH

[~]$ echo $PATH
/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/usr/lib64/ccache:/usr/games:/home/me/bin
4
27.01.2020, 19:33
  • 1
    Это очень опасно, потому что Вы добавляете запаздывание : к PATH (т.е. запись пустой строки), потому что затем текущий рабочий каталог является частью Вашего PATH. –  maxschlepzig 13.04.2014, 13:05
  • 2
    Это очень опасно, потому что Вы добавляете запаздывание : к PATH (т.е. запись пустой строки), потому что затем текущий рабочий каталог является частью Вашего PATH. –  maxschlepzig 13.04.2014, 13:05

Вот гладкий:

printf %s "$PATH" | awk -v RS=: -v ORS=: '!arr[$0]++'

Дольше (чтобы видеть, как это работает):

printf %s "$PATH" | awk -v RS=: -v ORS=: '{ if (!arr[$0]++) { print $0 } }'

Хорошо, так как Вы плохо знакомы с Linux, вот то, как на самом деле установить ПУТЬ без запаздывания ":"

PATH=`printf %s "$PATH" | awk -v RS=: '{ if (!arr[$0]++) {printf("%s%s",!ln++?"":":",$0)}}'`

btw удостоверяются, что НЕ имели каталоги, содержащие ":" в Вашем ПУТИ иначе это собирается быть испорченным.

некоторый кредит к:

13
27.01.2020, 19:33
  • 1
    1 это не работает. Я все еще вижу дубликаты в своем пути. –  dogbane 14.06.2012, 10:34
  • 2
    @dogbane: Это удаляет дубликаты для меня. Однако это имеет тонкую проблему. Вывод имеет a: на конце, который, если установлено как Ваш $PATH, означает, что текущий каталог добавляется путь. Это имеет последствия безопасности на многопользовательской машине. –  camh 14.06.2012, 10:42
  • 3
    @dogbane, это работает, и я отредактировал сообщение, чтобы иметь одну команду строки без запаздывания: –  akostadinov 14.06.2012, 10:59

Также sed (сюда использование GNU sed синтаксис), может сделать задание:

MYPATH=$(printf '%s\n' "$MYPATH" | sed ':b;s/:\([^:]*\)\(:.*\):\1/:\1\2/;tb')

эти работы хорошо только в случае, если первый путь . как в примере dogbane.

В общем случае необходимо добавить еще один s команда:

MYPATH=$(printf '%s\n' "$MYPATH" | sed ':b;s/:\([^:]*\)\(:.*\):\1/:\1\2/;tb;s/^\([^:]*\)\(:.*\):\1/:\1\2/')

Это работает даже над такой конструкцией:

$ echo "/bin:.:/foo/bar/bin:/usr/bin:/foo/bar/bin:/foo/bar/bin:/bar/bin:/usr/bin:/bin" \
| sed ':b;s/:\([^:]*\)\(:.*\):\1/:\1\2/;tb;s/^\([^:]*\)\(:.*\):\1/\1\2/'

/bin:.:/foo/bar/bin:/usr/bin:/bar/bin
2
27.01.2020, 19:33
PATH=`perl -e 'print join ":", grep {!$h{$_}++} split ":", $ENV{PATH}'`
export PATH

Это использует жемчуг и обладает несколькими преимуществами:

  1. Это удаляет дубликаты
  2. Это сохраняет порядок сортировки
  3. Это сохраняет самое раннее появление (/usr/bin:/sbin:/usr/bin приведет к /usr/bin:/sbin)
2
27.01.2020, 19:33

Я сделал бы это только с основными инструментами, такими как TR, вид и uniq:

NEW_PATH=`echo $PATH | tr ':' '\n' | sort | uniq | tr '\n' ':'`

Если нет ничего специального или странного в Вашем пути, оно должно работать

-1
27.01.2020, 19:33
  • 1
    btw, можно использовать sort -u вместо sort | uniq. –  rush 25.04.2013, 21:44
  • 2
    Так как порядок элементов ПУТИ является значительным, это не очень полезно. –  maxschlepzig 13.04.2014, 12:13
PATH=`awk -F: '{for (i=1;i<=NF;i++) { if ( !x[$i]++ ) printf("%s:",$i); }}' <<< "$PATH"`

Объяснение кода awk:

  1. Разделите вход двоеточиями.
  2. Добавьте новые записи пути в ассоциативный массив для быстрого дублирующегося поиска.
  3. Печатает ассоциативный массив.

В дополнение к тому, чтобы быть кратким, эта острота быстра: awk использует хеш-таблицу объединения в цепочку для достижения амортизируемого O (1) производительность.

на основе Удаления дублирующихся записей $PATH

1
27.01.2020, 19:33
  • 1
    Старое сообщение, но мог Вы объяснять: if ( !x[$i]++ ) .Спасибо. –  Cbhihe 12.07.2016, 14:36

Это - моя версия:

path_no_dup () 
{ 
    local IFS=: p=();

    while read -r; do
        p+=("$REPLY");
    done < <(sort -u <(read -ra arr <<< "$1" && printf '%s\n' "${arr[@]}"));

    # Do whatever you like with "${p[*]}"
    echo "${p[*]}"
}

Использование: path_no_dup "$PATH"

Демонстрационный вывод:

rany$ v='a:a:a:b:b:b:c:c:c:a:a:a:b:c:a'; path_no_dup "$v"
a:b:c
rany$
1
27.01.2020, 19:33
[113524]Последние bash-версии (>= 4) также ассоциативных массивов, i. e. для этого также можно использовать bash 'one liner':

  • где:
  • IFS[114384] меняет разделитель полей ввода на [114385]:
  • объявляет -A[114388] объявляет ассоциативный массив

Paste Special - 2

${a[$i]+_}[114390] значением расширения параметра: [114391]_[114392] подставляется, если и только если установлено [114393]a[$i][114394]. Это похоже на [114395]${parameter:+word}[114396], который также тестирует на not-null. Таким образом, в следующей оценке условия выражение [114397]_[114398] (т.е. строка из одного символа) оценивается как истинное (это эквивалентно [114399]-n _[114400]) - в то время как пустое выражение оценивается как ложное.

enter image description here

1
27.01.2020, 19:33
[113458] Вот лайнер AWK один.

где:

printf %s "$PATH"[114362] печатает содержимое [114363]$PATH[114364] без новой строки

RS=: [114366] изменяет символ входного разделителя записи (по умолчанию newline)

ORS=[114368] изменяет выходной разделитель записи на пустую строку

+---+---------------+------------+------------+
|   | A             | B          | C          |
+---+---------------+------------+------------+
| 1 | col1, line1a  |            |            |
|   | col1, line1b  |            |            |
|   | col1, line1c  | col2, row1 | col3, row1 |
| 2 | col1, row2    | col2, row2 | col3, row2 |
+---+---------------+------------+------------+

a[114370] имя неявно созданного массива

"col1, line1a
col1, line1b
col1, line1c","col2, row1","col3, row1"
"col1, row2","col2, row2","col3, row2"

$0[114372] ссылки текущая запись

a[$0][114374] является ассоциативным разыменованием массива

  • ++[114376] является пост-инкрементирующим оператором
  • ! a[$0]++[114378] охраняет правую сторону, т.е. он гарантирует, что текущая запись будет распечатана только в том случае, если она не была распечатана до

NR[114380] текущего номера записи, начинающегося с 1

+---+---------------+------------+------------+
|   | A             | B          | C          |
+---+---------------+------------+------------+
| 1 | "col1, line1a |            |            |
| 2 | col1, line1b  |            |            |
| 3 | col1, line1c" | col2, row1 | col3, row1 |
| 4 | col1, row2    | col2, row2 | col3, row2 |
+---+---------------+------------+------------+

Это означает, что AWK используется для разделения содержимого [113924]PATH[113925] вдоль разделительных символов [113926]:[113927] и для отфильтровывания дубликатов записей без изменения порядка.

Так как ассоциативные массивы AWK реализованы в виде хэш-таблиц, то время выполнения является линейным (i. e. в O(n)).

Обратите внимание, что нам не нужно искать символы в кавычках [113928]:[113929], так как оболочки [113930] не содержат кавычек[113931] для поддержки каталогов с [113932]:[113933] в имени в переменной [113934]PATH[113935].

Awk + insertstep 1

Вышеизложенное можно упростить вставкой:

  1. Команда [113936]insert[113937] используется для интерференции вывода awk с двоеточиями. Это упрощает действие awk для печати (которое является действием по умолчанию).
  2. Python
  3. То же самое, что и двухстрочный Python:
7
27.01.2020, 19:33

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

PATH="$(perl -e 'print join(":", grep { not $seen{$_}++ } split(/:/, $ENV{PATH}))')"

Оно просто расщепляется на двоеточие (split(/: /, $ENV{PATH})), использует grep {не $seen{$_}++ } для фильтрации любых повторяющихся экземпляров путей, кроме первого, а затем соединяет остальные обратно, разделенные двоеточиями, и печатает результат (print join(":", . ...)).

Если вам нужна ещё какая-то структура вокруг неё, а также возможность дедупликации других переменных, попробуйте этот фрагмент, который я сейчас использую в моём собственном конфигурационном файле:

# Deduplicate path variables
get_var () {
    eval 'printf "%s\n" "${'"$1"'}"'
}
set_var () {
    eval "$1=\"\$2\""
}
dedup_pathvar () {
    pathvar_name="$1"
    pathvar_value="$(get_var "$pathvar_name")"
    deduped_path="$(perl -e 'print join(":",grep { not $seen{$_}++ } split(/:/, $ARGV[0]))' "$pathvar_value")"
    set_var "$pathvar_name" "$deduped_path"
}
dedup_pathvar PATH
dedup_pathvar MANPATH

Этот код дедуплицирует и PATH, и MANPATH, и вы можете легко вызвать дедуп_патвар для других переменных, которые содержат разделенные двоеточиями списки путей (например, PYTHONPATH).

.
24
27.01.2020, 19:33

Решение - не такое элегантное, как те, которые изменяют переменные *RS, но, возможно, достаточно понятное:

PATH=`awk 'BEGIN {np="";split(ENVIRON["PATH"],p,":"); for(x=0;x<length(p);x++) {  pe=p[x]; if(e[pe] != "") continue; e[pe] = pe; if(np != "") np=np ":"; np=np pe}} END { print np }' /dev/null`

Вся программа работает в блоках BEGIN и END. Он извлекает переменную PATH из среды, разбивая ее на единицы. Затем он переитерирует результирующий массив p (который создается по порядку split()). Массив e является ассоциативным массивом, который используется для определения того, видели ли мы текущий элемент path (например, /usr/local/bin) ранее, а если нет, то добавляется к np, с логикой добавления двоеточия к np, если в np уже есть текст. Блок END просто повторяет np. Это можно было бы еще больше упростить, добавив флаг -F:, исключив третий аргумент в split() (как по умолчанию он используется в FS) и изменив np = np ":" к np = np FS, давая нам:

awk -F: 'BEGIN {np="";split(ENVIRON["PATH"],p); for(x=0;x<length(p);x++) {  pe=p[x]; if(e[pe] != "") continue; e[pe] = pe; if(np != "") np=np FS; np=np pe}} END { print np }' /dev/null

Наивно, я полагал, что for(element in array) сохранит порядок, но это не работает, поэтому мое оригинальное решение не работает, так как люди расстроятся, если кто-то вдруг скремблирует порядок их $PATH:

awk 'BEGIN {np="";split(ENVIRON["PATH"],p,":"); for(x in p) { pe=p[x]; if(e[pe] != "") continue; e[pe] = pe; if(np != "") np=np ":"; np=np pe}} END { print np }' /dev/null
0
27.01.2020, 19:33
export PATH=$(echo -n "$PATH" | awk -v RS=':' '(!a[$0]++){if(b++)printf(RS);printf($0)}')

Сохраняется только первое вхождение и поддерживается относительный порядок.

0
27.01.2020, 19:33

Пока мы добавляем одинарные строки, отличные от awk:

PATH=$(zsh -fc "typeset -TU P=$PATH p; echo \$P")

(Может быть так же просто, как PATH = $ (zsh -fc 'typeset -U path; echo $ PATH') но всегда zsh читает по крайней мере один файл конфигурации zshenv , который может изменять PATH .)

Он использует две замечательные функции zsh:

  • скаляры, привязанные к массивам ( typeset -T )
  • и массивы, которые автоматически удаляют повторяющиеся значения ( typeset -U ).
3
27.01.2020, 19:33

Как показали другие, это возможно в одной строке с использованием awk, sed, perl, zsh или bash, в зависимости от вашей терпимости к длинным строкам и удобочитаемости. Вот функция bash, которая

  • удаляет дубликаты
  • сохраняет порядок
  • разрешает пробелы в именах каталогов
  • позволяет вам указать разделитель (по умолчанию ':')
  • может использоваться с другими переменными, а не только PATH
  • работает в версиях bash <4, что важно, если вы используете OS X, которая из-за проблем с лицензированием не поддерживает bash версии 4

использование функции bash

remove_dups() {
    local D=${2:-:} path= dir=
    while IFS= read -d$D dir; do
        [[ $path$D =~ .*$D$dir$D.* ]] || path+="$D$dir"
    done <<< "$1$D"
    printf %s "${path#$D}"
}

Удаление дубликатов из PATH

PATH=$(remove_dups "$PATH")
2
27.01.2020, 19:33

Правильный способ удаления дубликатов, предложенный @ghm1014 и @rush (в комментарии )с использованиемsort:

PATH=`echo -e ${PATH//:/'\n'} | awk '{printf("%d|%s\n", NR, $0)}' | sort -t '|' -k 2 -u | sort -t '|' -k 1 -g | cut -f2 -d'|'`; export PATH=${PATH//[$'\n']/:}
1
19.12.2020, 18:51

Теги

Похожие вопросы