Препятствовать тому, чтобы текст был разделен между </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
!
Если у Вас уже нет дубликатов в 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
Использовать 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
(Обновленный для удаления запаздывания :
.)
Была подобная дискуссия об этом здесь.
Я проявляю определенный другой подход. Вместо того, чтобы просто принять ПУТЬ, который установлен от всех различных файлов инициализации, которые установлены, я предпочитаю использовать 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
:
к PATH
(т.е. запись пустой строки), потому что затем текущий рабочий каталог является частью Вашего PATH
.
– maxschlepzig
13.04.2014, 13:05
:
к 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 удостоверяются, что НЕ имели каталоги, содержащие ":" в Вашем ПУТИ иначе это собирается быть испорченным.
некоторый кредит к:
Также 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
PATH=`perl -e 'print join ":", grep {!$h{$_}++} split ":", $ENV{PATH}'`
export PATH
Это использует жемчуг и обладает несколькими преимуществами:
/usr/bin:/sbin:/usr/bin
приведет к /usr/bin:/sbin
)Я сделал бы это только с основными инструментами, такими как TR, вид и uniq:
NEW_PATH=`echo $PATH | tr ':' '\n' | sort | uniq | tr '\n' ':'`
Если нет ничего специального или странного в Вашем пути, оно должно работать
PATH=`awk -F: '{for (i=1;i<=NF;i++) { if ( !x[$i]++ ) printf("%s:",$i); }}' <<< "$PATH"`
Объяснение кода awk:
В дополнение к тому, чтобы быть кратким, эта острота быстра: awk использует хеш-таблицу объединения в цепочку для достижения амортизируемого O (1) производительность.
на основе Удаления дублирующихся записей $PATH
Это - моя версия:
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$
${a[$i]+_}[114390] значением расширения параметра: [114391]_[114392] подставляется, если и только если установлено [114393]a[$i][114394]. Это похоже на [114395]${parameter:+word}[114396], который также тестирует на not-null. Таким образом, в следующей оценке условия выражение [114397]_[114398] (т.е. строка из одного символа) оценивается как истинное (это эквивалентно [114399]-n _[114400]) - в то время как пустое выражение оценивается как ложное.
где:
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] является ассоциативным разыменованием массива
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 + insert
Вышеизложенное можно упростить вставкой:
Вот понятное однолайнерное решение, которое делает все правильно: удаляет дубликаты, сохраняет порядок следования путей и не добавляет двоеточие в конце. Таким образом, оно должно дать вам дедуплицированный 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).
Решение - не такое элегантное, как те, которые изменяют переменные *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
export PATH=$(echo -n "$PATH" | awk -v RS=':' '(!a[$0]++){if(b++)printf(RS);printf($0)}')
Сохраняется только первое вхождение и поддерживается относительный порядок.
Пока мы добавляем одинарные строки, отличные от 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
). Как показали другие, это возможно в одной строке с использованием awk, sed, perl, zsh или bash, в зависимости от вашей терпимости к длинным строкам и удобочитаемости. Вот функция bash, которая
использование функции 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")
Правильный способ удаления дубликатов, предложенный @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']/:}
PATH=$PATH:x=b
, x в первоначальном тракте мог бы иметь значение a, таким образом когда выполнят итерации в порядке, затем новое значение будет проигнорировано, но когда в обратном порядке, новое значение вступит в силу. – Eric Wang 03.09.2016, 17:38PATH=x:$PATH
. – Gilles 'SO- stop being evil' 03.09.2016, 18:42PATH=$PATH:...
нетPATH=...:$PATH
. Таким образом более следует выполнить итерации инвертированного порядка. Даже при том, что Вы, путь также работал бы, затем люди, добавляете в пути обратный путь. – Eric Wang 03.09.2016, 19:13