와일드 카드 확장에서 첫 번째 일치 항목을 얻으려면 어떻게해야합니까?


36

Bash 및 Zsh와 같은 쉘은 패턴과 일치하는 많은 인수가 와일드 카드를 인수로 확장합니다.

$ echo *.txt
1.txt 2.txt 3.txt

그러나 모든 경기가 아니라 첫 경기 만 반환하려면 어떻게해야합니까?

$ echo *.txt
1.txt

쉘 특정 솔루션은 신경 쓰지 않지만 파일 이름에서 공백으로 작동하는 솔루션을 원합니다.


ls * .txt | 머리 -1?
Archemar

1
@Archemar : 파일 이름의 줄 바꿈이 작동하지 않습니다.
Flimm

답변:


24

bash의 강력한 방법 중 하나는 배열로 확장하고 첫 번째 요소 만 출력하는 것입니다.

pattern="*.txt"
files=( $pattern )
echo "${files[0]}"  # printf is safer!

(넌 심지어 단지 수 echo $files, 누락 된 인덱스는 [0]로 처리된다.)

파일 이름을 확장 할 때 공간 / 탭 / 줄 바꾸기 및 기타 메타 문자를 안전하게 처리합니다. 사실상 로케일 설정은 "첫 번째"를 변경할 수 있습니다.

bash 완성 함수를 사용하여 대화식 으로이 작업을 수행 할 수도 있습니다 .

_echo() {
    local cur=${COMP_WORDS[COMP_CWORD]}   # string to expand

    if compgen -G "$cur*" > /dev/null; then
        local files=( ${cur:+$cur*} )   # don't expand empty input as *
        [ ${#files} -ge 1 ] && COMPREPLY=( "${files[0]}" )
    fi
}
complete -o bashdefault -F _echo echo

이것은 _echo함수를 바인드하여 echo명령에 인수를 완료합니다 (정상 완료를 재정의 함). 위의 코드에 추가 "*"가 추가되어 부분 파일 이름에서 탭을 누르면 올바른 일이 발생할 수 있습니다.

코드는 설정되거나 가정되는 것이 아니라 약간 복잡합니다. nullglob( shopt -s nullglob) compgen -Gglob를 일부 일치 항목으로 확장 한 다음 안전하게 배열로 확장하고 마지막으로 인용을 강력하게 설정하도록 COMPREPLY를 설정합니다.

bash 's로 부분적 으로이 작업을 수행 할 수 있지만 (프로그래밍 방식으로 glob 확장) compgen -Gstdout에 인용되지 않은 출력으로 강력하지 않습니다.

평소와 같이 완성은 다소 어려워 환경 변수를 포함한 다른 것들의 완성을 깨뜨립니다 ( 기본 동작 에뮬레이션에 대한 자세한 내용은 여기_bash_def_completion() 기능 참조 ).

compgen완성 함수 외부 에서도 사용할 수 있습니다 .

files=( $(compgen -W "$pattern") )

한 가지 주목할 점은 "~"는 구형이 아니며 $ variables 및 기타 확장과 마찬가지로 별도의 확장 단계에서 bash에 의해 처리됩니다. compgen -G파일 이름을 compgen -W붙일 뿐이지 만 bash의 기본 확장을 모두 제공 하지만 확장이 너무 많을 수도 있습니다 ( ``및 포함 $()). 달리 -G의이 -W 되어 안전하게 인용 (나는 차이를 설명 할 수 없다). -W토큰을 확장하는 것이 목적이므로, 이러한 파일이 존재하지 않더라도 "a"를 "a"로 확장하므로 이상적이지 않을 수 있습니다.

이것은 이해하기 쉽지만 원하지 않는 부작용이있을 수 있습니다.

_echo() {
    local cur=${COMP_WORDS[COMP_CWORD]}
    local files=( $(compgen -W "$cur") ) 
    printf -v COMPREPLY %q "${files[0]}"  
}

그때:

touch $'curious \n filename'

echo curious*tab

사용주의 printf %q안전하게 값을 인용 할 수 있습니다.

마지막 옵션 중 하나는 GNU 유틸리티에서 0으로 구분 된 출력을 사용하는 것입니다 ( bash FAQ 참조 ).

pattern="*.txt"
while IFS= read -r -d $'\0' filename; do 
    printf '%q' "$filename"; 
    break; 
done < <(find . -maxdepth 1 -name "$pattern" -printf "%f\0" | sort -z )

이 옵션을 사용하면 정렬 순서를 조금 더 제어 할 수 있습니다 (글로브를 확장 할 때의 순서는 로케일에 따라 다르고 LC_COLLATE케이스가 접히지 않을 수도 있습니다). 그렇지 않으면 작은 문제에 대해 다소 큰 망치입니다.


20

zsh에서는 [1] glob 한정자를 사용하십시오 . 이 특수 사례는 최대 하나의 일치 항목을 반환하더라도 여전히 목록이며 할당 (배열 할당은 제외)과 같은 단일 단어를 예상하는 컨텍스트에서는 글로브가 확장되지 않습니다.

echo *.txt([1])

ksh 또는 bash에서는 전체 일치 목록을 배열로 채우고 첫 번째 요소를 사용할 수 있습니다.

tmp=(*.txt)
echo "${tmp[0]}"

모든 쉘에서 위치 매개 변수를 설정하고 첫 번째 매개 변수를 사용할 수 있습니다.

set -- *.txt
echo "$1"

이 위치 매개 변수를 방해합니다. 원하지 않으면 서브 쉘을 사용할 수 있습니다.

echo "$(set -- *.txt; echo "$1")"

자체 위치 매개 변수 세트가있는 함수를 사용할 수도 있습니다.

set_to_first () {
  eval "$1=\"\$2\""
}
set_to_first f *.txt
echo "$f"

1
그리고 첫 $ n $의 경기를 얻으려면*.txt([1,n])
Emre

6

시험:

for i in *.txt; do printf '%s\n' "$i"; break; done
1.txt

파일 이름 확장은 현재 로케일에서 유효한 조합 순서에 따라 정렬됩니다.



1

나는 똑같은 것을 궁금해 하면서이 오래된 질문을 우연히 발견했습니다. 나는 이것으로 끝났다.

echo $(ls *.txt | head -n1)

당신은 물론, 대체 할 수 headtail-n1다른 번호.


이름에 줄 바꿈이있는 파일로 작업하는 경우 위의 내용이 작동하지 않습니다. 줄 바꿈으로 작업하려면 다음 중 하나를 사용할 수 있습니다.

  • ls -b *.txt | head -n1 | sed -E 's/\\n/\n/g' (BSD에서는 작동하지 않습니다)
  • ls -b *.txt | head -n1 | sed -e 's/\\n/\'$'\n/g'
  • ls -b *.txt | head -n1 | perl -pe 's/\\n/\n/g'
  • echo -e "$(ls -b *.txt | head -n1)" (특수 문자로 작동)

3
아니요, 파일 이름에 줄 바꿈이 있으면 실패합니다.
Isaac

6
파일 이름에 줄 바꿈이 포함 된 곳에 어떤 종류의 미친 세상이 있습니까?
billynoah

-1

내가 자주 접하는 유스 케이스는 glob 확장 후 top / bottom 디렉토리를 정의하는 것입니다 (예 : 버전이있는 SDK 또는 빌드 도구로 가득 찬 디렉토리). 이 상황에서는 일반적으로 쉘 스크립트 내의 몇몇 위치에서 사용하기 위해 해당 디렉토리 이름을 변수에 저장하려고합니다.

이 명령은 일반적으로 나를 위해 수행합니다.

export SDK_DIR=$(dirname /path/to/versioned/sdks/*/. | tail -n1)

면책 조항 : Glob 확장은 폴더를 semver별로 정렬하지 않습니다. 당신은 경고를 받았습니다. 당신이있는 경우에 이것은 대단한 Dockerfile디렉토리의 한 버전을하지만, 그 디렉토리 버전은 이미지에 이미지 다를 수 🤷


U & L에 오신 것을 환영합니다! 그것은 대부분의 디렉토리 이름을 처리하지만 줄 바꿈으로 디렉토리 이름을 처리하지는 않습니다. 이와 같은 디렉토리를 작성하고 mkdir "$(echo one; echo two)"의미하는 바를 보십시오 .
Flimm

다른 대안, 특히 사용하는 버전과 비교하여 장점은 무엇입니까 tail?
RalfFriedl

표준 dirname은 단일 경로 이름 만 사용하므로 특정 구현에서 지원하지 않는 한 여러 경로 이름에 대한 작업에 의존 할 수 없습니다.
Kusalananda

@Flimm 좋은 지적; 폴더 구조에 줄 바꿈이 있으면 대부분의 개발자가 처리해야 할 더 큰 문제가 있다고 생각합니다.
앤드류 오드리

@RalfFriedl 좋은 질문입니다; 이것은 본질적으로 올바른 디렉토리가 아닌 것은 걸러 (그리고하지 않습니다 목록 / 트래버스와 ....)
앤드류 오드리
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.