PATH 환경 변수에서 디렉토리의 복제본을 제거 할 수있는 bash 쉘 함수를 작성하려고합니다.
명령을 사용하여 한 줄 명령 으로이 작업을 수행 할 수 있다고 말 awk
했지만 어떻게 수행 해야하는지 알 수 없습니다. 아무도 어떻게 알아?
PATH 환경 변수에서 디렉토리의 복제본을 제거 할 수있는 bash 쉘 함수를 작성하려고합니다.
명령을 사용하여 한 줄 명령 으로이 작업을 수행 할 수 있다고 말 awk
했지만 어떻게 수행 해야하는지 알 수 없습니다. 아무도 어떻게 알아?
답변:
에 중복 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
PATH=$PATH:x=b
, 원래 PATH의 x는 값 a를 가질 수 있으므로 순서대로 반복하면 새로운 값은 무시되지만 역순으로 새로운 것은 무시됩니다 가치가 적용됩니다.
PATH=x:$PATH
입니다.
PATH=$PATH:...
not PATH=...:$PATH
. 따라서 역순을 반복하는 것이 더 적절합니다. 당신이 방법도 효과가 있지만 사람들은 반대 방향으로 덧붙입니다.
다음 은 모든 올바른 작업을 수행 하는 이해 하기 쉬운 단일 라이너 솔루션입니다. 중복을 제거하고 경로 순서를 유지하며 끝에 콜론을 추가하지 않습니다. 따라서 원본과 정확히 동일한 동작을 제공하는 중복 제거 된 PATH를 제공해야합니다.
PATH="$(perl -e 'print join(":", grep { not $seen{$_}++ } split(/:/, $ENV{PATH}))')"
단순히 콜론 ( split(/:/, $ENV{PATH})
)으로 분할되고 grep { not $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를 중복 제거하며 dedup_pathvar
콜론으로 구분 된 경로 목록 (예 : PYTHONPATH)을 보유하는 다른 변수를 쉽게 호출 할 수 있습니다 .
chomp
후행 줄 바꿈을 제거하기 위해 를 추가해야했습니다 . 이것은 나를 위해 일했다 :perl -ne 'chomp; print join(":", grep { !$seen{$_}++ } split(/:/))' <<<"$PATH"
매끄러운 것이 있습니다 :
printf %s "$PATH" | awk -v RS=: -v ORS=: '!arr[$0]++'
더 길게 (작동 방식보기) :
printf %s "$PATH" | awk -v RS=: -v ORS=: '{ if (!arr[$0]++) { print $0 } }'
좋아, 리눅스를 처음 사용하기 때문에, 뒤에 ":"없이 실제로 PATH를 설정하는 방법이있다
PATH=`printf %s "$PATH" | awk -v RS=: '{ if (!arr[$0]++) {printf("%s%s",!ln++?"":":",$0)}}'`
btw는 PATH에 ":"를 포함하는 디렉토리가 없어야합니다. 그렇지 않으면 엉망이됩니다.
약간의 크레딧 :
echo -n
. 귀하의 명령은 "여기에 문자열"로 작동하지 않는 예를 들어보십시오 :awk -v RS=: -v ORS=: '!arr[$0]++' <<< ".:/foo/bin:/bar/bin:/foo/bin"
다음은 AWK 원 라이너입니다.
$ PATH=$(printf %s "$PATH" \
| awk -vRS=: -vORS= '!a[$0]++ {if (NR>1) printf(":"); printf("%s", $0) }' )
어디:
printf %s "$PATH"
$PATH
줄 바꿈없이 내용을 인쇄합니다.RS=:
입력 레코드 구분 문자를 변경합니다 (기본값은 줄 바꿈).ORS=
출력 레코드 분리 문자를 빈 문자열로 변경a
내재적으로 작성된 배열의 이름$0
현재 레코드를 참조a[$0]
연관 배열 역 참조입니다.++
증가 후 연산자입니다!a[$0]++
오른쪽을 보호합니다. 즉, 현재 레코드가 인쇄되지 않은 경우에만 인쇄되도록합니다.NR
1로 시작하는 현재 레코드 번호즉, AWK는 구분 문자를 PATH
따라 내용 을 분할 :
하고 순서를 수정하지 않고 중복 항목을 필터링하는 데 사용됩니다 .
AWK 연관 배열은 해시 테이블로 구현되므로 런타임은 선형 적입니다 (즉, O (n)).
:
쉘 은 변수 :
에서 이름으로 디렉토리를 지원 하기 위해 인용 부호 를 제공하지 않기 때문에 인용 문자를 찾을 필요 가 없습니다PATH
.
페이스트로 위를 단순화 할 수 있습니다.
$ PATH=$(printf %s "$PATH" | awk -vRS=: '!a[$0]++' | paste -s -d:)
이 paste
명령은 awk 출력을 콜론으로 분산시키는 데 사용됩니다. 이것은 인쇄에 대한 awk 동작 (기본 동작)을 단순화합니다.
파이썬 2 라이너와 동일 :
$ PATH=$(python3 -c 'import os; from collections import OrderedDict; \
l=os.environ["PATH"].split(":"); print(":".join(OrderedDict.fromkeys(l)))' )
paste
난 뒤에 추가하지 않으면 명령은 나를 위해 작동하지 않습니다 -
STDIN을 사용합니다.
-v
. 그렇지 않으면 오류가 발생합니다. -v RS=: -v ORS=
. 다른 맛의 awk
구문.
이것에 대해서도 비슷한 토론이 있었습니다 .
나는 조금 다른 접근법을 취합니다. 설치되는 다른 모든 초기화 파일에서 설정된 PATH를 그대로 사용하는 대신 getconf
시스템 경로를 식별하고 먼저 배치 한 다음 선호하는 경로 순서를 추가 한 다음 awk
중복을 제거하는 데 사용 하는 것이 좋습니다 . 이것은 실제로 명령 실행 속도를 높이거나 높이 지 않을 수도 있지만 이론적으로 더 안전합니다.
# 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
.
우리는 비 ak oneliners를 추가하는 한 :
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
).또한 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
다른 사람들이 awk, sed, perl, zsh 또는 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")
이것은 내 버전입니다.
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$
최근의 bash 버전 (> = 4)도 연관 배열입니다. 즉, bash 'one liner'를 사용할 수도 있습니다.
PATH=$(IFS=:; set -f; declare -A a; NR=0; for i in $PATH; do NR=$((NR+1)); \
if [ \! ${a[$i]+_} ]; then if [ $NR -gt 1 ]; then echo -n ':'; fi; \
echo -n $i; a[$i]=1; fi; done)
어디:
IFS
입력 필드 구분자를 :
declare -A
연관 배열을 선언${a[$i]+_}
매개 변수 확장 의미 입니다. 설정된 _
경우에만 대체됩니다 a[$i]
. 이것은 ${parameter:+word}
null이 아닌지 테스트하는 것과 비슷합니다 . 따라서 다음 조건부 평가에서 표현식 _
(예 : 단일 문자열)은 true로 평가되고 (이와 동일 함 -n _
) 빈 표현식은 false로 평가됩니다.${a[$i]+_}
답변을 편집하고 글 머리 기호 하나를 추가 하여 특정 구문을 설명 할 수 있습니다 . 나머지는 완벽하게 이해할 수 있지만 당신은 저를 잃었습니다. 감사합니다.
PATH=`awk -F: '{for (i=1;i<=NF;i++) { if ( !x[$i]++ ) printf("%s:",$i); }}' <<< "$PATH"`
awk 코드 설명 :
간결한 것 외에도이 one-liner는 빠릅니다. awk는 연결 해시 테이블을 사용하여 상각 된 O (1) 성능을 얻습니다.
중복 $ PATH 항목 제거를 기반으로
if ( !x[$i]++ )
.. 감사.
해결책-* 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 는 현재 경로 요소 (예 : / usr / local / bin )를 보았는지 여부를 결정하는 데 사용되는 연관 배열 이며, 그렇지 않은 경우 콜론을 추가하는 로직과 함께 np 에 추가됩니다. np 에 이미 텍스트가 있으면 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
tr, sort 및 uniq와 같은 기본 도구를 사용하여 수행합니다.
NEW_PATH=`echo $PATH | tr ':' '\n' | sort | uniq | tr '\n' ':'`
경로에 특별하거나 이상한 것이 없다면 작동해야합니다.
sort -u
대신을 사용할 수 있습니다 sort | uniq
.