Bash for 루프, 문자열 var에 공백이 포함되어 있습니다.

Bash for 루프, 문자열 var에 공백이 포함되어 있습니다.

내 디렉토리에는 공백이 있는 두 개의 파일 foo baranother file공백이 없는 두 개의 파일이 있습니다 file1.file2

다음 스크립트가 작동합니다.

for f in foo\ bar another\ file; do file "$f"; done

이 스크립트는 다음과 같은 경우에도 작동합니다.

for f in 'foo bar' 'another file'; do file "$f"; done

그러나 다음 스크립트는 작동하지 않습니다.

files="foo\ bar another\ file"
for f in $files; do file "$f"; done

이 스크립트도 작동하지 않습니다.

files="'foo bar' 'another file'"
for f in $files; do file "$f"; done

그러나 파일에 공백이 없으면 스크립트가 작동합니다.

files="file1 file2"
for f in $files; do file "$f"; done

감사해요!

편집하다

내 스크립트의 코드 조각:

while getopts "i:a:c:d:f:g:h" arg; do
  case $arg in
    i) files=$OPTARG;;
    # ...
  esac
done

for f in $files; do file "$f"; done

공백이 없는 파일의 경우 내 스크립트가 작동합니다. 하지만 다음 방법 중 하나로 공백이 포함된 파일을 매개변수로 전달하는 스크립트를 실행하고 싶습니다.

./script.sh -i "foo\ bar another\ file"
./script.sh -i foo\ bar another\ file
./script.sh -i "'foo bar' 'another file'"
./script.sh -i 'foo bar' 'another file'

답변1

다음을 사용하는 경우 bash배열을 사용할 수 있습니다.

#!/bin/bash
files=('foo bar' 'another file' file1 'file2')
for f in "${files[@]}"; do file -- "$f"; done

공백이나 쉘 구문의 기타 특수 문자(예 ;: , |, 등)가 포함된 파일 이름은 따옴표로 묶어야 합니다.&*

와일드카드( , , 및 일부 버전에서는 활성화된 경우 더 많음) 또는 문자(공백, 탭 및 줄 바꿈) 파일 이름 기본값을 포함하려면 최소한 목록 컨텍스트(in "${files[@]}"및 here 와 같이)에서 확장 주위에 큰따옴표가 필요합니다 . ."$f"*?[\bashextglob$IFS

이러한 문자를 포함하지 않고 ASCII가 아닌 문자를 포함하지 않는 파일 이름의 경우 선택 사항입니다(권장합니다).

파일 목록이 현재 작업 디렉터리에서 오는 경우 원하는 대로 와일드카드를 사용할 수 있습니다. 예를 들어 이름에 포함된 파일이나 디렉터리를 files=(*f*)일치시키 려면 일치하지 않는 경우 리터럴을 미리 가져오는 것을 피하고 싶습니다. (그러나 배열을 완전히 사용하거나 피할 수도 있습니다).fshopt -s nullglob*f*for f in *f*; do...done

--태그는 file대시로 시작하는 경우에도 후속 인수가 파일 이름임을 알려줍니다.

man bash(검색 Arrays)을 사용하거나 자세한 내용을 읽어보세요 info bash arrays.

답변2

게시한 네 가지 스크립트 호출 간에는 약간의 차이점이 있습니다.

./script.sh -i "'foo bar' 'another file'"
./script.sh -i "foo\ bar another\ file"

위의 두 매개변수 모두 첫 번째 매개변수로 스크립트에 전달되고 -i, 두 번째 매개변수로 단일 문자열이 전달됩니다. 첫 번째는 이고 'foo bar' 'another file', 두 번째는 입니다 foo\ bar another\ file. 쉘 언어에서는 둘 다 두 개의 문자열(또는 파일 이름) foo bar및 를 나타냅니다 another file. 그러나 인용 및 백슬래시 처리는 문자열이 문자열로 끝나기 때문에 변수 내부에 있는 경우가 아니라 문자열이 원래 명령줄에 있는 경우에만 적용됩니다.

./script.sh -i foo\ bar another\ file
./script.sh -i 'foo bar' 'another file'

반면에, 이 두 사람은 총매개변수: -i, foo bar, 및 another file.

다양한 매개변수를 안전하게 처리하는 것이 훨씬 쉽기 때문에 구별이 다소 중요합니다. 그대로 유지하면 그 안에 포함된 따옴표와 이스케이프를 처리할 필요가 없습니다.

또한 중요한 것은 유사한 작업을 실행하면 script ./*.txt파일 이름이 다른 매개변수로 전달된다는 것입니다.

예를 들어, 다음과 같이 호출하면 file두 개의 파일 만 호출됩니다 script 'foo bar' another\ file.

#! /bin/sh -
for f in "$@"; do
    file -- "$f"
done

또는 더 간단하고 이식성이 뛰어난 경우 for기본적으로 위치 인수를 반복합니다.

#! /bin/sh -
for f do
    file -- "$f"
done

하지만 getopts도 있습니다. 그리고 파일 이름을 다른 매개변수로 사용하면 첫 번째 이름만 사용됩니다 -i. 여기에는 기본적으로 두 가지 공통 옵션이 있습니다.

사용자에게 -i옵션을 재사용하여 파일 이름을 배열로 수집하도록 하십시오.

#! /bin/bash -
files=()
while getopts "i:" arg; do
  case $arg in
    (i) files+=("$OPTARG");;
  esac
done

for f in "${files[@]}"; do file -- "$f"; done

script -i "foo bar" -i "another file"(run은 script -i file1 file2무시됩니다 . file2) 마찬가지로 다른 옵션을 통해 제공된 파일 이름을 수집하기 위해 다른 배열을 추가할 수 있습니다.

또는 옵션이 스크립트가 작동하는 "모드"를 설정하고 파일 이름을 옵션과 다른 목록으로 갖도록 합니다. getopts모든 매개변수를 그대로 유지하고 처리된 매개변수만 삭제하면 됩니다 shift. 그래서:

#! /bin/bash -

unset -v mode
while getopts "ie" arg; do
  case $arg in
    (i) mode=i;;
    (e) mode=e;;
    (*) : handle usage error;;
  esac
done
shift "$((OPTIND - 1))"

case "$mode" in
  (i) for f do file -- "$f"; done;;
  (e) echo "do something else with the files";;
  (*) echo "error: mode not specified" >&2;;
esac

script -i "foo bar" "another file"그런 다음 or script -i -- -file-starting-with-dash- -other-file-또는 으로 실행하십시오 script -i -- *.

getopts여기서는 당신이 accept 이외의 다른 일을 했다고 가정합니다 -i. 그렇지 않으면 플래그를 완전히 포기했을 수도 있기 때문입니다. :) 그러나 어떤 다른 옵션이 있는지에 따라 가장 합리적인(또는 관례적인) 솔루션이 어느 정도 영향을 받습니다.

또한 루프가 파일만 호출하는 경우 루프를 file실행 file -- "${files[@]}"하고 건너뛸 수 있습니다.file -- "$@"


그러나 이 작업을 수행하려면 다음을 수행하십시오.

script -i foo bar -e doo daa

스크립트가 파일에 대해 foo한 가지 작업을 수행 하고 및 bar에 대해 doo다른 작업을 수행하게 daa하면 다른 질문이 됩니다. 물론 할 수는 있지만 getopts그것을 달성하기 위한 도구는 아닐 수도 있습니다.

또한보십시오:

그리고 물론:

단일 변수에서 여러 개의 다른 임의 문자열(파일 이름)을 처리하려고 할 때 발생하는 문제.

답변3

명령줄 구문 분석의 경우 경로 이름 피연산자가 항상 명령줄의 마지막 피연산자가 되도록 정렬합니다.

./myscript -a -b -c -- 'foo bar' 'another file' file[12]

옵션 구문 분석은 다음과 같습니다.

a_opt=false b_opt=false c_opt=false
while getopts abc opt; do
     case $opt in
         a) a_opt=true ;;
         b) b_opt=true ;;
         c) c_opt=true ;;
         *) echo error >&2; exit 1
    esac
done

shift "$(( OPTIND - 1 ))"

for pathname do
    # process pathname operand "$pathname" here
done

shift경로 이름 피연산자가 위치 인수 목록에 남아 있도록 처리된 옵션이 밖으로 이동되도록 합니다 .

이것이 가능하지 않은 경우 -i옵션을 여러 번 지정하도록 허용하고 루프에서 해당 인수가 발견될 때마다 배열에 지정된 인수를 수집합니다.

pathnames=() a_opt=false b_opt=false c_opt=false
while getopts abci: opt; do
     case $opt in
         a) a_opt=true ;;
         b) b_opt=true ;;
         c) c_opt=true ;;
         i) pathnames+=( "$OPTARG" ) ;;
         *) echo error >&2; exit 1
    esac
done

shift "$(( OPTIND - 1 ))"

for pathname in "${pathnames[@]}"; do
    # process pathname argument "$pathname" here
done

이것은 불릴 것이다

./myscript -a -b -c -i 'foo bar' -i 'another file' -i file1 -i file2

답변4

Bash는 공백이 포함된 변수를 잘 처리하지 못합니다. 이 작업을 수행하려면 아래와 같이 "이름 바꾸기" 기능을 사용해야 합니다.

데비안과 유사한 시스템을 사용하는 경우 다음을 사용하십시오.

sudo apt-get install rename

Red Hat과 같은 시스템을 사용하는 경우 다음을 사용하십시오.

sudo yum install install rename

파일이 위치한 디렉토리로 이동

cd your_target_directory

공백 문자가 포함된 모든 파일/변수의 이름을 바꿉니다. 이 단계를 거치면 공간은 더 이상 문제가 되지 않습니다.

rename 's/ /_/g' *

여기서부터 계속하는 데 문제가 없습니다...즐기세요

관련 정보