code

배시 루프 목록에서 공백을 이스케이프하려면 어떻게 해야 합니까?

starcafe 2023. 4. 28. 21:13
반응형

배시 루프 목록에서 공백을 이스케이프하려면 어떻게 해야 합니까?

특정 디렉터리의 모든 하위 디렉터리(파일 제외)를 순환하는 bash 셸 스크립트가 있습니다.문제는 일부 디렉토리 이름에 공백이 포함되어 있다는 것입니다.

테스트 디렉토리의 내용은 다음과 같습니다.

$ls -F test
Baltimore/  Cherry Hill/  Edison/  New York City/  Philadelphia/  cities.txt

그리고 디렉터리를 순환하는 코드:

for f in `find test/* -type d`; do
  echo $f
done

다음은 출력입니다.

테스트/볼티모어테스트/체리테스트/에디슨테스트/신규요크도시필라델피아 시험

Cherry Hill과 New York City는 2개 또는 3개의 개별 항목으로 처리됩니다.

다음과 같이 파일 이름을 인용해 보았습니다.

for f in `find test/* -type d | sed -e 's/^/\"/' | sed -e 's/$/\"/'`; do
  echo $f
done

하지만 소용이 없습니다.

이것을 할 수 있는 간단한 방법이 있을 것입니다.


아래의 답변은 훌륭합니다.그러나 이 문제를 더욱 복잡하게 만들기 위해 테스트 디렉토리에 나열된 디렉토리를 항상 사용하지는 않습니다.디렉토리 이름을 대신 명령줄 매개 변수로 전달하고 싶을 때가 있습니다.

IFS를 설정하자는 찰스의 제안을 받아들여 다음과 같은 것을 생각해 냈습니다.

dirlist="${@}"
(
  [[ -z "$dirlist" ]] && dirlist=`find test -mindepth 1 -type d` && IFS=$'\n'
  for d in $dirlist; do
    echo $d
  done
)

명령줄 인수에 공백이 없는 경우(인수가 따옴표로 묶인 경우에도) 이 작업이 제대로 수행됩니다.를 들어,를 이렇게 것입니다: 예를들스트다같음호이출다니합과를크어.test.sh "Cherry Hill" "New York City"과 같은출력을 합니다.

체리신규요크도시

첫째, 그런 식으로 하지 마세요.가장 좋은 접근 방식은 다음을 사용하는 것입니다.find -exec적절하게:

# this is safe
find test -type d -exec echo '{}' +

은 NUL이지만, 을 찾을 수 . NUL 종료 목록을 사용해야 합니다.-print0:

# this is safe
while IFS= read -r -d '' n; do
  printf '%q\n' "$n"
done < <(find test -mindepth 1 -type d -print0)

찾기에서 배열을 채우고 나중에 해당 배열을 전달할 수도 있습니다.

# this is safe
declare -a myarray
while IFS= read -r -d '' n; do
  myarray+=( "$n" )
done < <(find test -mindepth 1 -type d -print0)
printf '%q\n' "${myarray[@]}" # printf is an example; use it however you want

검색결지지않는경우되를 지원하지 ,-print0그러면 결과가 안전하지 않습니다. 이름에 새 줄이 포함된 파일이 있는 경우(예, 합법적인 파일인 경우) 다음과 같이 원하는 대로 작동하지 않습니다.

# this is unsafe
while IFS= read -r n; do
  printf '%q\n' "$n"
done < <(find test -mindepth 1 -type d)

방법 중 세 사용은 단어 전에 때문에 입니다.IFS공백 문자를 포함하지 않는 변수입니다.글로빙해제(해제▁turn▁glob글▁offbingset -f)와[],*또는?확장으로부터:

# this is unsafe (but less unsafe than it would be without the following precautions)
(
 IFS=$'\n' # split only on newlines
 set -f    # disable globbing
 for n in $(find test -mindepth 1 -type d); do
   printf '%q\n' "$n"
 done
)

마지막으로 명령줄 매개 변수의 경우 셸에서 지원하는 어레이(즉, ksh, bash 또는 zsh)를 사용해야 합니다.

# this is safe
for d in "$@"; do
  printf '%s\n' "$d"
done

분리를 유지할 것입니다.인용문(및 의 사용)에 유의하십시오.$@$*이 중요합니다이 중요합니다.배열은 글로벌 식과 같은 다른 방법으로도 채울 수 있습니다.

# this is safe
entries=( test/* )
for d in "${entries[@]}"; do
  printf '%s\n' "$d"
done
find . -type d | while read file; do echo $file; done

그러나 파일 이름에 새 줄이 포함된 경우에는 작동하지 않습니다.위의 방법은 실제로 디렉터리 이름을 변수에 넣고 싶을 때 사용할 수 있는 유일한 방법입니다.일부 명령만 실행하려면 xargs를 사용합니다.

find . -type d -print0 | xargs -0 echo 'The directory is: '

다음은 파일 이름의 탭 및/또는 공백을 처리하는 간단한 솔루션입니다.파일 이름의 다른 이상한 문자를 새 줄처럼 처리해야 하는 경우 다른 답을 선택합니다.

테스트 디렉토리

ls -F test
Baltimore/  Cherry Hill/  Edison/  New York City/  Philadelphia/  cities.txt

디렉토리에 들어갈 코드

find test -type d | while read f ; do
  echo "$f"
done

로 묶어야 ."$f"을 인수로 사용할 경우.따옴표를 사용하지 않으면 공백이 인수 구분 기호로 작동하고 호출된 명령에 여러 인수가 지정됩니다.

그리고 출력:

test/Baltimore
test/Cherry Hill
test/Edison
test/New York City
test/Philadelphia

이것은 표준 유닉스에서는 매우 까다롭고 대부분의 솔루션은 줄 바꿈이나 다른 문자로 실행됩니다.에는 GNU 툴 를 이용할 수 .find-print0 및사를 합니다.xargs하는 옵션이 있는 -0(영문-영문).단순 파일 이름에는 슬래시와 NUL '\0'이라는 두 문자가 표시되지 않습니다.분명히 슬래시는 경로 이름으로 나타나므로 이름의 끝을 표시하기 위해 NUL '\0'을 사용하는 GNU 솔루션은 기발하고 바보가 되지 않습니다.

다음을 사용하여 IFS(내부 필드 구분 기호)를 일시적으로 사용할 수 있습니다.

OLD_IFS=$IFS     # Stores Default IFS
IFS=$'\n'        # Set it to line break
for f in `find test/* -type d`; do
    echo $f
done

IFS=$OLD_IFS

<!>

그냥 놔두는 게 어때요?

IFS='\n'

명령어 앞에서?필드 구분 기호가 <스페이스><탭><새줄>에서 <새줄>로만 변경됩니다.

find . -print0|while read -d $'\0' file; do echo "$file"; done

사용합니다

SAVEIFS=$IFS
IFS=$(echo -en "\n\b")
for f in $( find "$1" -type d ! -path "$1" )
do
  echo $f
done
IFS=$SAVEIFS

그걸로 충분하지 않을까요?
http://www.cyberciti.biz/tips/handling-filenames-with-spaces-in-bash.html 에서 가져온 아이디어

목록을 문자열로 저장하지 않고 배열로 저장하여 이러한 구분 기호의 혼동을 방지합니다.다음은 테스트의 모든 하위 디렉터리에서 작동하거나 명령줄에 제공된 목록에서 작동하는 스크립트의 예입니다.

#!/bin/bash
if [ $# -eq 0 ]; then
        # if no args supplies, build a list of subdirs of test/
        dirlist=() # start with empty list
        for f in test/*; do # for each item in test/ ...
                if [ -d "$f" ]; then # if it's a subdir...
                        dirlist=("${dirlist[@]}" "$f") # add it to the list
                fi
        done
else
        # if args were supplied, copy the list of args into dirlist
        dirlist=("$@")
fi
# now loop through dirlist, operating on each one
for dir in "${dirlist[@]}"; do
        printf "Directory: %s\n" "$dir"
done

이제 한두 개의 곡선이 삽입된 테스트 디렉토리에서 이를 시도해 보겠습니다.

$ ls -F test
Baltimore/
Cherry Hill/
Edison/
New York City/
Philadelphia/
this is a dirname with quotes, lfs, escapes: "\''?'?\e\n\d/
this is a file, not a directory
$ ./test.sh 
Directory: test/Baltimore
Directory: test/Cherry Hill
Directory: test/Edison
Directory: test/New York City
Directory: test/Philadelphia
Directory: test/this is a dirname with quotes, lfs, escapes: "\''
'
\e\n\d
$ ./test.sh "Cherry Hill" "New York City"
Directory: Cherry Hill
Directory: New York City

ps 만약 그것이 단지 입력의 공간에 관한 것이라면, 몇몇 이중 따옴표들은 나에게 부드럽게 작동했습니다...

read artist;

find "/mnt/2tb_USB_hard_disc/p_music/$artist" -type f -name *.mp3 -exec mpg123 '{}' \;

Jonathan이 말한 것에 추가하기: 사용-print0을 선택할 수 .find xargs다음과 같이:

find test/* -type d -print0 | xargs -0 command

그면명령실니다됩행이 이 실행됩니다.command적절한 인수를 사용하면 공백이 있는 디렉터리가 적절하게 따옴표로 묶입니다(즉, 하나의 인수로 전달됨).

#!/bin/bash

dirtys=()

for folder in *
do    
 if [ -d "$folder" ]; then    
    dirtys=("${dirtys[@]}" "$folder")    
 fi    
done    

for dir in "${dirtys[@]}"    
do    
   for file in "$dir"/\*.mov   # <== *.mov
   do    
       #dir_e=`echo "$dir" | sed 's/[[:space:]]/\\\ /g'`   -- This line will replace each space into '\ '   
       out=`echo "$file" | sed 's/\(.*\)\/\(.*\)/\2/'`     # These two line code can be written in one line using multiple sed commands.    
       out=`echo "$out" | sed 's/[[:space:]]/_/g'`    
       #echo "ffmpeg -i $out_e -sameq -vcodec msmpeg4v2 -acodec pcm_u8 $dir_e/${out/%mov/avi}"    
       `ffmpeg -i "$file" -sameq -vcodec msmpeg4v2 -acodec pcm_u8 "$dir"/${out/%mov/avi}`    
   done    
done

위의 코드는 .mov 파일을 .avi로 변환합니다..mov 파일은 다른 폴더에 있으며 폴더 이름에도 공백이 있습니다.위 스크립트는 .mov 파일을 같은 폴더에 있는 .avi 파일로 변환합니다.그게 당신들에게 도움이 되는지 모르겠습니다.

사례:

[sony@localhost shell_tutorial]$ ls
Chapter 01 - Introduction  Chapter 02 - Your First Shell Script
[sony@localhost shell_tutorial]$ cd Chapter\ 01\ -\ Introduction/
[sony@localhost Chapter 01 - Introduction]$ ls
0101 - About this Course.mov   0102 - Course Structure.mov
[sony@localhost Chapter 01 - Introduction]$ ./above_script
 ... successfully executed.
[sony@localhost Chapter 01 - Introduction]$ ls
0101_-_About_this_Course.avi  0102_-_Course_Structure.avi
0101 - About this Course.mov  0102 - Course Structure.mov
[sony@localhost Chapter 01 - Introduction]$ CHEERS!

건배!

경로 이름의 공백도 처리해야 했습니다.으로 한 와 결국제한재이것귀고이었용한를은일가▁a,for item in /path/*:

function recursedir {
    local item
    for item in "${1%/}"/*
    do
        if [ -d "$item" ]
        then
            recursedir "$item"
        else
            command
        fi
    done
}

파일 목록을 Bash 배열로 변환합니다.이는 Matt McClure의 접근 방식을 사용하여 Bash 함수에서 배열을 반환합니다. http://notes-matthewlmcclure.blogspot.com/2009/12/return-array-from-bash-function-v-2.html 결과는 다중 줄 입력을 Bash 배열로 변환하는 방법입니다.

#!/bin/bash

# This is the command where we want to convert the output to an array.
# Output is: fileSize fileNameIncludingPath
multiLineCommand="find . -mindepth 1 -printf '%s %p\\n'"

# This eval converts the multi-line output of multiLineCommand to a
# Bash array. To convert stdin, remove: < <(eval "$multiLineCommand" )
eval "declare -a myArray=`( arr=(); while read -r line; do arr[${#arr[@]}]="$line"; done; declare -p arr | sed -e 's/^declare -a arr=//' ) < <(eval "$multiLineCommand" )`"

for f in "${myArray[@]}"
do
   echo "Element: $f"
done

이 접근 방식은 잘못된 문자가 있는 경우에도 작동하는 것으로 보이며 모든 입력을 Bash 배열로 변환하는 일반적인 방법입니다.단점은 입력이 길면 Bash의 명령줄 크기 제한을 초과하거나 메모리를 대량으로 사용할 수 있다는 것입니다.

목록에서 최종적으로 작동하는 루프가 있는 접근 방식은 (사용자에게 입력을 요청하는 등) stdin을 읽는 것이 쉽지 않다는 단점이 있으며 루프는 새로운 프로세스이므로 루프가 완료된 후 루프 내부에서 설정한 변수를 사용할 수 없는 이유가 궁금할 수 있습니다.

저는 또한 IFS를 설정하는 것을 싫어합니다. 다른 코드를 망칠 수 있습니다.

저는 너무 많은 복잡한 답들을 봅니다.find 유틸리티의 출력을 전달하거나 루프를 작성하고 싶지 않습니다. find에는 이에 대한 "exec" 옵션이 있기 때문입니다.

제 문제는 dbf 확장자가 있는 모든 파일을 현재 폴더로 이동하고 싶었고 일부 파일에는 공백이 포함되어 있었습니다.

나는 그렇게 태클을 걸었습니다.

 find . -name \*.dbf -print0 -exec mv '{}'  . ';'

나한테는 훨씬 간단해 보입니다.

방금 질문과 당신의 질문 사이에 몇 가지 유사점이 있다는 것을 알게 되었습니다.명령에 인수를 전달하려는 경우에도 마찬가지입니다.

test.sh "Cherry Hill" "New York City"

그것들을 순서대로 인쇄하기 위해

for SOME_ARG in "$@"
do
    echo "$SOME_ARG";
done;

$@는 큰따옴표로 둘러싸여 있습니다. 여기에 메모가 있습니다.

특정 폴더에서 여러 디렉터리 또는 파일을 순차적으로 압축하려면 동일한 개념이 필요했습니다.저는 ls에서 리스트를 파싱하고 이름에 공백이 생기는 문제를 피하기 위해 awk를 사용하여 해결했습니다.

source="/xxx/xxx"
dest="/yyy/yyy"

n_max=`ls . | wc -l`

echo "Loop over items..."
i=1
while [ $i -le $n_max ];do
item=`ls . | awk 'NR=='$i'' `
echo "File selected for compression: $item"
tar -cvzf $dest/"$item".tar.gz "$item"
i=$(( i + 1 ))
done
echo "Done!!!"

당신은 어떻게 생각하나요?

find Downloads -type f | while read file; do printf "%q\n" "$file"; done

이것은 저에게 효과가 있으며, 거의 "깨끗"합니다.

for f in "$(find ./test -type d)" ; do
  echo "$f"
done

그냥 단순한 변형 문제가....flv를 입력한 파일을 .mp3(yawn)로 변환합니다.

for file in read `find . *.flv`; do ffmpeg -i ${file} -acodec copy ${file}.mp3;done

모든 Macintosh 사용자 플래시 파일을 재귀적으로 찾아 오디오(복사본, 트랜스코드 없음)로 변환...위에서 언급한 것과 같습니다. 단순히 파일을 위한 것이 아니라 읽기입니다.탈출할 것입니다.

언급URL : https://stackoverflow.com/questions/301039/how-can-i-escape-white-space-in-a-bash-loop-list

반응형