경로가 인수로 제공되면 'For' 루프가 목록을 반복하지 않습니다.

경로가 인수로 제공되면 'For' 루프가 목록을 반복하지 않습니다.

연습 중이야쉘 스크립트디렉터리를 매개 변수로 사용하고 디렉터리에 있는 각 파일을 반복하여 이름과 크기를 출력하는 간단한 스크립트를 만들려고 합니다.

#!/bin/bash
# A practice shell script to try and display a list of file names
# and their sizes using the output of ls -l and cut.
declare -i index
export index=0
export name=""
export size=0

for file in $1 ; do
   index+=1
   name=`basename $file`
   size=`ls -l $file | cut -d " " -f 5`
   echo "$index: $name, size: $size bytes"
done

매개변수로 제공 하면 ./*파일에 대해 이 작업을 수행하고 그게 전부입니다. 그러나 위의 코드를 편집하여 ./*위치 에 넣으면 $1현재 디렉터리의 모든 파일이 작동하고 반복됩니다.

$1와 같아야 하는데 왜 같은 일을 하지 않는 걸까요 ./*?

답변1

그 이유는 스크립트를 호출하는 쉘이 와일드카드 패턴을 확장하기 때문입니다../* 앞으로스크립트로 전달됩니다. 즉, 와일드카드 패턴이 일치하면 file1.txt스크립트 가 다음과 file4.txt같이 호출됩니다 .

./my_script.sh ./*

실제로는 다음과 같이 해석됩니다.

./my_script.sh file1.txt file2.txt file3.txt file4.txt

이는 쉘 스크립트에 표시되는 매개변수입니다.

자세한 내용은 다음을 확인하세요.쉘 확장 순서에 관한 부분Bash 참조 매뉴얼에 있습니다.

이 문제를 해결하는 방법에는 두 가지가 있습니다.

  1. 특정 디렉터리의 모든 파일을 항상 반복하고 싶다면 해당 디렉터리를 매개변수로 전달한 다음 반복하세요.
    for f in "$1"/*
    do
        # operations on "$f"
    done
    
  2. 또는 작업하려는 파일 이름만 전달하는 것이 확실하다면 다음과 같이 전체 인수 목록을 반복하십시오.
    for f in "$@"
    do
        # operations on "$f"
    done
    

확실히 흥미로운 연습인 스크립트에 glob 패턴을 전달하여 이 작업을 수행하려는 경우에도 가능합니다(@ilkkachu의 설명 참조). @fra-san이 의견에서 언급했듯이 이 접근 방식에는 장점이 있습니다. 스크립트 사용에 더 많은 유연성을 추가하고 셸 명령줄 인수의 제한을 우회합니다("인수 목록이 너무 깁니다" 참조). RAM은 여전히 ​​길이를 제한하지만 생성된 파일 이름 목록) - 하지만 각별히 주의해야 합니다.

  • 매개변수를 따옴표(작은따옴표 또는 큰따옴표)로 묶거나 백슬래시로 glob 문자를 이스케이프하여 쉘이 glob을 확장하는 것을 방지할 수 있습니다.
    ./my_script.sh "./*"
    ./my_script.sh './*'
    ./my_script.sh ./\*
    
  • 스크립트 내에서 위치 매개변수를 참조합니다.$1 인용되지 않음이런 식으로 실제로 쉘에 의해 해석됩니다(우리가 종종 피하고 싶은 것).
  • "해석"에는 확장(위 참조)뿐만 아니라 단어 분할도 포함되므로입력 필드 구분 기호 IFS토큰화가 발생하지 않도록 빈 문자열로 바꿉니다.
  • 루프는 for다음과 같습니다
    IFS=
    for f in $1
    do
        # Operations on "$f"
    done
    

스크립트에 대한 몇 가지 일반적인 참고 사항:

  • 항상 쉘 변수를 인용하십시오., 특히 파일 이름이 포함된 경우 그렇지 않으면 스크립트에서 공백이나 기타 훨씬 더 이국적인 문자가 포함된 파일 이름을 발견하게 됩니다. 파일 이름에는 개행 문자도 허용된다는 점을 기억하세요(으악)!
  • ls다음의 출력을 구문 분석합니다.매우 좌절비슷한 이유로. 특정 파일의 속성을 식별하려는 경우 이 stat도구가 더 나은 선택입니다 . 예를 들어, 파일 크기를 확인하려면 다음을 사용할 수 있습니다.
    size=$(stat --printf="%s" "$f")
    
  • 이것은존경받는$( ... )명령 대체를 위해 이전 "백틱 스타일" 대신 "새" 스타일을 사용하십시오 ` ... `.
  • 쉘 스크립트를 확인하는 것이 좋습니다shellcheck는 이러한 (및 기타) 가능한 오류 소스로부터 보호하기 위해 많은 Linux 배포판에서 독립 실행형 도구로도 사용할 수 있습니다.

답변2

Bash가 와일드카드와 매개변수를 해석하는 방법을 이해해야 합니다. 와일드카드가 있으면 bash는 이를 즉시 해석하고 일치하는 모든 파일로 바꿉니다. $1을 ./*로 바꾸면 이런 일이 발생합니다. 현재 디렉터리에서 모든 파일을 가져와서 반복합니다.

너가 가질 때

for file in $1 ; do

첫 번째 매개변수만 필요합니다. 그것은 당신이 원하는 것이 아닙니다. 모든 파일을 반복하려면 다음을 사용해야 합니다.

for file in $* ; do

(이것은 모든 매개변수를 허용합니다)

또는 -를 사용하여 반복할 수 있습니다. 이렇게 하면 shift다른 인수가 있을 때마다 첫 번째 인수가 제거됩니다.

while [ $# -gt 0 ]
do
    file=$1
    shift
    ...
done

관련 정보