bash glob을 문자열 변수로 만드는 방법은 무엇입니까?

시스템 정보

OS : OS X

bash : GNU bash, 버전 3.2.57 (1) -release (x86_64-apple-darwin16)

배경

타임 머신이 모든 git / nodejs 프로젝트에서 일련의 디렉토리와 파일을 제외하도록하고 싶습니다. 내 프로젝트 디렉토리가 ~/code/private/ ~/code/public/ 에 있으므로 bash 루핑을 사용하여 tmutil를 수행합니다.

문제

짧은 버전

계산 된 문자열 변수 k, for- loop :

i="~/code/public/*" j="*.launch" k=$i/$j # $k="~/code/public/*/*.launch" for i in $k # I need $k to glob here do echo $i done 

아래의 긴 버전에는 k=$i/$j가 표시됩니다. 따라서 문자열을 하드 코딩 할 수 없습니다. for 루프입니다.

긴 버전

#!/bin/bash exclude=" *.launch .classpath .sass-cache Thumbs.db bower_components build connect.lock coverage dist e2e/*.js e2e/*.map libpeerconnection.log node_modules npm-debug.log testem.log tmp typings " dirs=" ~/code/private/* ~/code/public/* " for i in $dirs do for j in $exclude do k=$i/$j # It is correct up to this line for l in $k # I need it glob here do echo $l # Command I want to execute # tmutil addexclusion $l done done done 

출력

그들은 글 로빙되지 않았습니다. 내가 원하는 것이 아닙니다.

~/code/private/*/*.launch ~/code/private/*/.DS_Store ~/code/private/*/.classpath ~/code/private/*/.sass-cache ~/code/private/*/.settings ~/code/private/*/Thumbs.db ~/code/private/*/bower_components ~/code/private/*/build ~/code/private/*/connect.lock ~/code/private/*/coverage ~/code/private/*/dist ~/code/private/*/e2e/*.js ~/code/private/*/e2e/*.map ~/code/private/*/libpeerconnection.log ~/code/private/*/node_modules ~/code/private/*/npm-debug.log ~/code/private/*/testem.log ~/code/private/*/tmp ~/code/private/*/typings ~/code/public/*/*.launch ~/code/public/*/.DS_Store ~/code/public/*/.classpath ~/code/public/*/.sass-cache ~/code/public/*/.settings ~/code/public/*/Thumbs.db ~/code/public/*/bower_components ~/code/public/*/build ~/code/public/*/connect.lock ~/code/public/*/coverage ~/code/public/*/dist ~/code/public/*/e2e/*.js ~/code/public/*/e2e/*.map ~/code/public/*/libpeerconnection.log ~/code/public/*/node_modules ~/code/public/*/npm-debug.log ~/code/public/*/testem.log ~/code/public/*/tmp ~/code/public/*/typings 

댓글

  • 작은 따옴표는 Bash에서 쉘 보간을 중지하므로 변수를 큰 따옴표로 묶어 볼 수 있습니다.
  • @ThomasN 아니요, 작동하지 않습니다. k는 계산 된 문자열이며 sta가 필요합니다. y 루프까지 그렇게. 긴 버전을 확인해주세요.
  • @ThomasN 더 명확하게하기 위해 짧은 버전을 업데이트했습니다.

답변

eval를 사용하여 또 다른 평가를 강제 할 수 있지만 실제로는 필요하지 않습니다. (그리고 eval가 시작됩니다. 파일 이름에 $와 같은 특수 문자가 포함 된 순간 심각한 문제가 발생합니다.) 문제는 “글 로빙이 아니라 물결표 확장입니다.

글 로빙이 발생합니다. 변수 확장 후 변수가 인용되지 않은 경우 (*) :

$ x="/tm*" ; echo $x /tmp 

그래서, 같은 맥락에서 이것은 당신이했던 것과 유사하고 작동합니다.

$ mkdir -p ~/public/foo/ ; touch ~/public/foo/x.launch $ i="$HOME/public/*"; j="*.launch"; k="$i/$j" $ echo $k /home/foo/public/foo/x.launch 

그러나 물결표를 사용하면 그렇지 않습니다.

$ i="~/public/*"; j="*.launch"; k="$i/$j" $ echo $k ~/public/*/*.launch 

이것은 Bash에 대해 명확하게 문서화 되었습니다.

확장 순서 : 중괄호 확장, 물결표 확장, 매개 변수 및 변수 확장, …

물결 확장은 변수 확장 전에 발생하므로 변수 내부의 물결표는 확장되지 않습니다. 쉬운 해결 방법은 $HOME 또는 전체 경로를 대신 사용하는 것입니다.

(* 변수에서 glob을 확장하는 것은 일반적으로 원하는 것이 아닙니다)


다른 사항 :

여기에서와 같이 패턴을 반복 할 때 :

exclude="foo *bar" for j in $exclude ; do ... 

$exclude는 인용되지 않았으므로이 시점에서 분할되고 또한 globbed됩니다. 따라서 현재 디렉토리에 패턴과 일치하는 항목이 포함되어 있으면 다음과 같이 확장됩니다.

$ i="$HOME/public/foo" $ exclude="*.launch" $ touch $i/real.launch $ for j in $exclude ; do # split and glob, no match echo "$i"/$j ; done /home/foo/public/foo/real.launch $ touch ./hello.launch $ for j in $exclude ; do # split and glob, matches in current dir! echo "$i"/$j ; done /home/foo/public/foo/hello.launch # not the expected result 

이 문제를 해결하려면 분할 된 문자열 대신 배열 변수 를 사용하세요.

$ exclude=("*.launch") $ exclude+=("something else") $ for j in "${exclude[@]}" ; do echo "$i"/$j ; done /home/foo/public/foo/real.launch /home/foo/public/foo/something else 

추가 보너스로 배열 항목은 분할 문제없이 공백을 포함 할 수도 있습니다.


유사한 작업을 수행 할 수 있습니다. find -path, 대상 파일이 어떤 디렉토리 레벨이어야하는지 신경 쓰지 않는다면. 예를 들어 /e2e/*.js로 끝나는 경로를 찾으려면 :

$ dirs="$HOME/public $HOME/private" $ pattern="*/e2e/*.js" $ find $dirs -path "$pattern" /home/foo/public/one/two/three/e2e/asdf.js 

iv 이전과 같은 이유로 ~ 대신 id = “09073477e5”>

,$dirsfind명령 줄이 분할되도록하지만$pattern는 셸에 의해 실수로 확장되지 않도록 따옴표로 묶어야합니다.

(GNU find에서 -maxdepth를 사용하여 관심이 있다면 검색의 깊이를 제한 할 수 있다고 생각합니다. 그러나 그것은 “약간 다른 문제입니다.)”

댓글

  • find에 대한 답변이 하나입니까? for-loop가 복잡해지고 있으므로 실제로 그 경로를 탐색하고 있습니다. 하지만 ‘ -path ‘에 문제가 있습니다.
  • 물결표에 대한 귀하의 정보 ‘ ~ ‘는 주요 문제에 더 직접적입니다. 최종 대본과 설명을 다른 답변에 게시하겠습니다. 하지만 여러분 께 전적인 감사를드립니다. : D
  • @JohnSiu, 네, 처음 떠오른 것은 find 사용이었습니다. 정확한 필요에 따라 사용할 수도 있습니다. (또는 일부 용도의 경우 더 좋습니다.)
  • @kevinarpe, 배열은 기본적으로이를위한 것이라고 생각합니다. 예, "${array[@]}" (따옴표 포함! )이 문서화되어 있습니다 ( 여기 여기 참조). 더 분할합니다.
  • @sixtyfive, [abc] 글로브 패턴 의 표준 부분입니다. div id = “716cf7cb1f”> , ‘ 여기에서 모든 항목을 다루기 위해 ‘ 필요하다고 생각하지 않습니다. .

Answer

나중에 사용하기 위해 문자열 대신 배열로 저장할 수 있습니다. 당신이 그것을 정의 할 때 globbing이 일어나게하십시오. 귀하의 경우 예를 들면 다음과 같습니다.

 k=(~/code/public/*/*.launch) for i in "${k[@]}"; do  

또는 이후 예에서는 “일부 문자열 eval 필요

 dirs=(~/code/private/* ~/code/public/*) for i in "${dirs[@]}"; do for j in $exclude; do eval "for k in $i/$j; do tmutil addexclusion \"\$k\"; done" done done  

댓글

  • $exclude에 와일드 카드가 어떻게 포함되어 있는지 확인하세요. ‘ d는 split + glob 연산자를 사용하기 전에 globbing을 비활성화하고 $i/$j 사용하지 않도록 복원해야합니다. eval를 사용하지만 "$i"/$j
  • 를 사용합니다.

  • 당신과 일까 츄 모두 좋은 대답을합니다. 그러나 그의 대답은 문제를 식별했습니다.

Answer

@ilkkachu 답변이 주요 문제를 해결했습니다. p>

V1

그러나 exclude에는 와일드 카드 (*)가있는 항목과없는 항목이 모두 포함되어 있으며 존재하지 않을 수도 있습니다. 결국, $i/$j를 globbing 한 후에 추가 검사가 필요합니다. 여기에서 찾은 결과를 공유하고 있습니다.

#!/bin/bash exclude=" *.launch .DS_Store .classpath .sass-cache .settings Thumbs.db bower_components build connect.lock coverage dist e2e/*.js e2e/*.map libpeerconnection.log node_modules npm-debug.log testem.log tmp typings " dirs=" $HOME/code/private/* $HOME/code/public/* " # loop $dirs for i in $dirs; do for j in $exclude ; do for k in $i/$j; do echo -e "$k" if [ -f $k ] || [ -d $k ] ; then # Only execute command if dir/file exist echo -e "\t^^^ Above file/dir exist! ^^^" fi done done done 

출력 설명

다음은 상황을 설명하는 부분 출력입니다.

/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/a.launch ^^^ Above file/dir exist! ^^^ /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/b.launch ^^^ Above file/dir exist! ^^^ /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.DS_Store ^^^ Above file/dir exist! ^^^ 

위는 자명합니다.

/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.classpath /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.sass-cache /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.settings /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/Thumbs.db /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/bower_components /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/build /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/connect.lock /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/coverage /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/dist 

위는 제외 항목 ($j)에는 와일드 카드가 없으며 $i/$j는 일반 문자열 연결이됩니다. 그러나 파일 / 디렉터리가 존재하지 않습니다.

/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/e2e/*.js /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/e2e/*.map 

위는 제외 항목 ($j)으로 표시됩니다. 와일드 카드이지만 일치하는 파일 / 디렉토리가없는 경우 $i/$j를 globbing하면 원래 문자열이 반환됩니다.

V2

V2는 작은 따옴표를 사용합니다. 깨끗한 결과를 얻으려면 evalshopt -s nullglob. 파일 / 디렉터리 최종 확인이 필요하지 않습니다.

#!/bin/bash exclude=" *.launch .sass-cache Thumbs.db bower_components build connect.lock coverage dist e2e/*.js e2e/*.map libpeerconnection.log node_modules npm-debug.log testem.log tmp typings " dirs=" $HOME/code/private/* $HOME/code/public/* " for i in $dirs; do for j in $exclude ; do shopt -s nullglob eval "k=$i/$j" for l in $k; do echo $l done shopt -u nullglob done done 

댓글

  • 한 가지 문제는 for j in $exclude, $exclude의 glob은 해당 $exclude 확장시 (그리고 eval에 문제가 있음). ‘ for i in $dirfor l in $k에 대해 글 로빙을 사용하도록 설정하고 싶지만

    . 후자 앞에는 ‘ set -f를, 다른쪽에는 set +f를 원합니다. 보다 일반적으로 ‘ 사용하기 전에 split + glob 연산자를 조정하려고합니다. 어쨌든 ‘ echo $l에 대해 split + glob을 원하지 않으므로 $l 거기에 인용되어야합니다.

  • @St é phaneChazelas v1 또는 v2를 언급하고 있습니까? v2의 경우 excludedirs 모두 작은 따옴표 (), so no globbing till eval`) 안에 있습니다.
  • 글 로빙은 목록 컨텍스트에서 인용되지 않은 변수 확장 에서 발생합니다. 즉, 변수를 인용하지 않은 채로 둡니다. 때때로 split + glob 연산자를 호출합니다. ‘ 스칼라 변수에 대한 할당이 globbing되지 않습니다. foo=*foo='*'는 동일하지만 echo $fooecho "$foo"는 동일하지 않습니다 (bash, ‘는 zsh, fish 또는 rc와 같은 셸에서 수정되었습니다. 위 링크도 참조하세요. / i>이 연산자를 사용하고 싶지만 일부에서는 분할 부분 만 사용하고 일부에서는 glob 부분 만 사용합니다.
  • @St é phaneChazelas 감사합니다. 정보 !!! 언젠가 나를 데려 갔지만 지금은 걱정을 이해합니다. 이것은 매우 귀중합니다 !! 감사합니다 !!!

답변

zsh 사용 :

 exclude=" *.launch .classpath .sass-cache Thumbs.db ... " dirs=( ~/code/private/* ~/code/public/* ) for f ($^dirs/${^${=~exclude}}(N)) { echo $f }  

${^array}string$array[1]string $array[2]string.... $=var는 변수에 대해 단어 분할을 수행하는 것이며 (다른 쉘이 기본적으로 수행하는 작업입니다!), $~var는 변수 (다른 또한 기본적으로 쉘 (일반적으로 원하지 않는 경우 다른 쉘에서 위의 $f를 인용해야 함)).

(N)는 해당 $^array1/$^array2 nullglob 을 켜는 glob 한정자입니다.

확장. 그러면 glob이 일치하지 않을 때 아무 것도 확장되지 않습니다. 또한~/code/private/foo/Thumbs.db와 같은 non-glob을 하나로 변환합니다. 즉, 특정 항목이 존재하지 않으면 포함되어 있지 않습니다.

코멘트

  • 정말 좋습니다. 테스트를 거쳐 작동합니다. 그러나 zsh는 다음과 같은 경우 줄 바꿈에 더 민감 해 보입니다. 작은 따옴표를 사용합니다. exclude를 묶는 방식이 출력에 영향을줍니다.
  • @JohnSiu, 오, 그래요. ‘ 맞습니다. split + glob 및 $^array는 빈 요소가 삭제되었는지 확인하기 위해 별도의 두 단계로 수행해야합니다 (편집 참조). zsh의 버그가 발생하면 ‘ 메일 링리스트에 문제를 올릴 것입니다.
  • v2를 만들었습니다. 더 깔끔하지만 zsh 스크립트만큼 작지 않은 bash의 경우 lol

Answer

Old 게시하지만 여기에서 우연히 발견했습니다. 다른 사람들에게 도움이 될 수 있습니다. 🙂

bash에는 일종의 glob () 함수가 있습니다 … compgen -G라고합니다. 한 줄에 값을 출력하므로 작동하려면 readarray가 필요합니다.

p>

해보기 :

i="~/code/public/*" j="*.launch" k=$i/$j # $k="~/code/public/*/*.launch" readarray -t files < <(compgen -G "$k") # $k is globbed here for i in "${files[@]}" do echo "Found -$i-" done 

답글 남기기

이메일 주소를 발행하지 않을 것입니다. 필수 항목은 *(으)로 표시합니다