짧은 답변 (답변에 가장 가깝지만 공백 처리)
OIFS="$IFS" IFS=$"\n" for file in `find . -type f -name "*.csv"` do echo "file = $file" diff "$file" "/some/other/path/$file" read line done IFS="$OIFS"
더 나은 답변 (파일 이름의 와일드 카드 및 줄 바꿈도 처리)
find . -type f -name "*.csv" -print0 | while IFS= read -r -d "" file; do echo "file = $file" diff "$file" "/some/other/path/$file" read line </dev/tty done
우수 답변 ( Gilles 기준 ” 답변 )
find . -type f -name "*.csv" -exec sh -c " file="$0" echo "$file" diff "$file" "/some/other/path/$file" read line </dev/tty " exec-sh {} ";"
또는 더 나은 방법은 파일 당 :
find . -type f -name "*.csv" -exec sh -c " for file do echo "$file" diff "$file" "/some/other/path/$file" read line </dev/tty done " exec-sh {} +
긴 답변
다음과 같은 세 가지 문제가 있습니다.
- 기본적으로 셸은 명령 출력을 공백, 탭 및 줄 바꿈으로 분할합니다.
- 파일 이름에는 다음과 같은 와일드 카드 문자가 포함될 수 있습니다. 확장 될 수 있습니다.
- 이름이
*.csv
로 끝나는 디렉토리가 있으면 어떻게 되나요?
1. 줄 바꿈에서만 분할
file
를 무엇으로 설정해야하는지 파악하려면 쉘이 출력을 가져와야합니다. find
의 전체 결과물이됩니다. 그렇지 않으면 file
가 find
의 전체 출력이됩니다. .
셸은 기본적으로 <space><tab><newline>
로 설정된 IFS
변수를 읽습니다.
그런 다음 find
의 출력에서 각 문자를 확인합니다. IFS
에있는 문자를 보면 파일 이름의 끝을 표시한다고 생각하여 file
를 설정합니다. 지금까지 본 어떤 문자로든 루프를 실행합니다. 그런 다음 다음 파일 이름을 얻기 위해 중단 된 지점부터 시작하여 출력 끝에 도달 할 때까지 다음 루프 등을 실행합니다.
따라서 이것은 효과적으로 다음을 수행합니다.
for file in "zquery" "-" "abc" ...
입력을 줄 바꿈으로 만 분할하도록 지시하려면 다음을 수행해야합니다.
IFS=$"\n"
for ... find
명령 전에.
IFS
를 하나의 줄 바꿈이므로 공백과 탭이 아닌 줄 바꿈으로 만 분할됩니다.
sh
또는 dash
ksh93
, bash
또는 zsh
대신 IFS=$"\n"
대신 다음과 같이하십시오.
IFS=" "
아마 충분합니다. 스크립트가 작동하도록하려면 다른 코너 케이스를 제대로 처리하려면 다음을 읽어보세요.
2. 와일드 카드없이 $file
확장
실행하는 루프 내부
diff $file /some/other/path/$file
셸은 $file
를 확장하려고합니다 (다시!).
공백을 포함 할 수 있지만 이미
위의 경우 문제가되지 않습니다.
그러나 *
또는 ?
와 같은 와일드 카드 문자를 포함 할 수도있어 예상치 못한 동작이 발생할 수 있습니다. (이 점을 지적 해준 Gilles에게 감사드립니다.)
셸에 와일드 카드 문자를 확장하지 않도록 지시하려면 변수를 큰 따옴표 안에 넣으십시오. 예 :
diff "$file" "/some/other/path/$file"
같은 문제가 우리를 물릴 수도 있습니다
for file in `find . -name "*.csv"`
예를 들어,이 세 파일이 있다면
file1.csv file2.csv *.csv
(가능성은 낮지 만 여전히 가능함)
실행 한 것처럼 보입니다.
for file in file1.csv file2.csv *.csv
확장 될
for file in file1.csv file2.csv *.csv file1.csv file2.csv
file1.csv
및 file2.csv
두 번 처리됩니다.
대신
find . -name "*.csv" -print | while IFS= read -r file; do echo "file = $file" diff "$file" "/some/other/path/$file" read line </dev/tty done
read
표준 입력에서 줄을 읽고 IFS
에 따라 줄을 단어로 분할 한 다음 지정한 변수 이름에 저장합니다.
여기에서 우리는 그것을 말합니다. 줄을 단어로 나누지 않고 줄을 $file
에 저장합니다.
또한 가 read line </dev/tty
로 변경되었습니다.
루프 내부에서 표준 입력이 find
파이프 라인을 통해.
만약 read
를 수행했다면 파일 이름의 일부 또는 전부를 사용하고 일부 파일을 건너 뛸 수 있습니다. .
/dev/tty
는 사용자가 스크립트를 실행하는 터미널입니다. 크론을 통해 스크립트를 실행하면 오류가 발생하지만이 경우에는 중요하지 않다고 가정합니다.
그런 다음 파일 이름에 줄 바꿈이 포함되면 어떻게됩니까?
-print
를 -print0
로 변경하고 마지막에 read -d ""
를 사용하여이를 처리 할 수 있습니다. 파이프 라인 :
find . -name "*.csv" -print0 | while IFS= read -r -d "" file; do echo "file = $file" diff "$file" "/some/other/path/$file" read char </dev/tty done
이렇게하면 find
가 각 파일 이름 끝에 널 바이트를 넣습니다. Null 바이트는 파일 이름에 허용되지 않는 유일한 문자이므로 이상하더라도 가능한 모든 파일 이름을 처리해야합니다.
다른 쪽에서 파일 이름을 얻으려면 .
위에서 read
를 사용한 곳에서는 줄 바꿈의 기본 줄 구분자를 사용했지만 지금은 find
는 줄 구분 기호로 null을 사용합니다. bash
에서는 인수의 NUL 문자를 명령 (내장 된 문자 포함)에 전달할 수 없지만 bash
는 -d ""
는 NUL로 구분 을 의미합니다. 따라서 -d ""
를 사용하여 read
find
와 같은 줄 구분 기호를 사용합니다. -d $"\0"
도 마찬가지로 작동합니다. bash
NUL 바이트를 지원하지 않으면 빈 문자열로 처리됩니다.
올바르게하기 위해 백 슬래시를 처리하지 않는다는 -r
도 추가합니다. 특별히 파일 이름. 예를 들어 -r
가 없으면 \<newline>
가 제거되고 \n
가
.
bash
또는 또는 null 바이트에 대한 위의 모든 규칙 기억 (Gilles 덕분에) :
find . -name "*.csv" -exec sh -c " file="$0" echo "$file" diff "$file" "/some/other/path/$file" read char </dev/tty " exec-sh {} ";"
* 3. .csv
find . -name "*.csv"
로 끝나는 이름은 something.csv
.
이를 방지하려면 -type f
를 find
명령에 추가하십시오.
find . -type f -name "*.csv" -exec sh -c " file="$0" echo "$file" diff "$file" "/some/other/path/$file" read line </dev/tty " exec-sh {} ";"
glenn jackman 이 지적했듯이이 두 예에서 각 파일에 대해 실행할 명령은 다음과 같습니다. 서브 쉘에서 실행되므로 루프 내에서 변수를 변경하면 잊혀집니다.
변수를 설정해야하고 여전히 설정해야하는 경우 루프의 끝에서 다음과 같은 프로세스 대체를 사용하도록 다시 작성할 수 있습니다.
i=0 while IFS= read -r -d "" file; do echo "file = $file" diff "$file" "/some/other/path/$file" read line </dev/tty i=$((i+1)) done < <(find . -type f -name "*.csv" -print0) echo "$i files processed"
명령 줄에 복사하여 붙여 넣으려고하면 , read line
는 echo "$i files processed"
를 소비하므로 명령이 실행되지 않습니다.
이를 방지하려면 read line </dev/tty
를 제거하고 결과를 less
와 같은 호출기로 보낼 수 있습니다.
참고
내부의 세미콜론 (;
)을 제거했습니다. 고리. 원하는 경우 다시 넣을 수 있지만 필요하지 않습니다.
요즘에는 $(command)
가 `command`
. 이는 `command1 \`command2\``
보다 “$(command1 $(command2))
작성이 더 쉽기 때문입니다.
read char
는 실제로 문자를 읽지 않습니다.전체 줄을 읽어서 read line
로 변경했습니다.
코멘트
파일 이름에 공백 또는 쉘 글 로빙 문자가 포함 된 경우이 스크립트는 실패합니다. \[?*
. find
명령은 한 줄에 하나의 파일 이름을 출력합니다. 그런 다음 명령 대체 `find …`
는 다음과 같이 쉘에 의해 평가됩니다.
-
find
명령을 실행합니다. 출력을 가져옵니다.
-
find
출력을 별도의 단어로 분할합니다. 공백 문자는 단어 구분 기호입니다.
- 각 단어에 대해 글 로빙 패턴 인 경우 일치하는 파일 목록으로 확장합니다.
예 : 현재 디렉토리에 `foo* bar.csv
, foo 1.txt
및 foo 2.txt
라는 세 개의 파일이 있다고 가정합니다.
-
find
명령은 ./foo* bar.csv
를 반환합니다.
- 쉘은이 문자열을 분할합니다. 공간에서
./foo*
및 bar.csv
라는 두 단어를 생성합니다.
-
./foo*
에는 globbing 메타 문자가 포함되어 있으며 “./foo 1.txt
및 ./foo 2.txt
와 같은 일치하는 파일 목록으로 확장됩니다.
- 따라서
for
루프는 ./foo 1.txt
, ./foo 2.txt
및
.
이 단계에서 단어 분할을 줄여서 대부분의 문제를 피할 수 있습니다. 글 로빙을 제거합니다. 단어 분할을 어둡게하려면 IFS
변수를 단일 개행 문자로 설정하십시오. 이렇게하면 find
의 출력이 줄 바꿈으로 만 분할되고 공백이 유지됩니다. 글 로빙을 끄려면 set -f
를 실행하세요. 그러면 파일 이름에 개행 문자가 포함되지 않는 한 코드의이 부분이 작동합니다.
IFS=" " set -f for file in $(find . -name "*.csv"); do …
(이것은 문제의 일부가 아니지만 저는 `…`
보다 $(…)
를 사용하는 것이 좋습니다. 의미는 같지만 역 따옴표 버전에는 이상한 인용 규칙이 있습니다.)
아래에 또 다른 문제가 있습니다. diff $file /some/other/path/$file
는
diff "$file" "/some/other/path/$file"
그렇지 않으면 $file
는 단어로 분할되고 단어는 위의 substitutio 명령과 같이 glob 패턴으로 처리됩니다. 쉘 프로그래밍에 대해 한 가지 기억해야하는 경우 다음을 기억하십시오. 변수 확장 ($foo
) 및 명령 대체 ( $(bar)
) (분할 의사를 모르는 경우). (위에서 우리는 find
출력을 여러 줄로 나누기를 원했습니다.)
find
는 찾은 각 파일에 대해 명령을 실행하도록 지시합니다.
find . -name "*.csv" -exec sh -c " echo "$0" diff "$0" "/some/other/path/$0" " {} ";"
이 경우 또 다른 접근 방식은 두 디렉토리를 비교하는 것입니다. 모든 “지루한”파일을 명시 적으로 제외합니다.
diff -r -x "*.txt" -x "*.ods" -x "*.pdf" … . /some/other/path
댓글