직접 호출과 쉘 변수의 bufferin 찾기 출력이 다릅니다.

직접 호출과 쉘 변수의 bufferin 찾기 출력이 다릅니다.

명령을 사용할 때 이상한 동작이 발생 find하지만 설명을 찾을 수 없습니다.

한 줄에 파일 이름이 1개인 .txt 파일이 있고 find 명령을 사용하여 데이터베이스에서 파일을 재귀적으로 검색하고 있습니다. 다음과 같은 명령을 사용할 때:

for filename in `cat filelist.csv`; do
find /location*/time*/ -name *${filename}*txt
done

한 줄에 1개의 출력이 예상되는 결과를 얻습니다. 그러나 동일한 명령을 사용하지만 출력을 변수로 설정하는 경우(결국 그렇게 해야 함):

for filename in `cat filelist.csv`; do
out=`find /location*/time*/ -name *${filename}*txt`
echo ${out}
done

find 명령은 같은 줄의 폴더에서 일치하는 모든 파일을 인쇄하는 것 같습니다. 두 가지 질문이 있습니다.

  1. 이 동작의 원인은 무엇입니까?
  2. find일치하는 각 파일(폴더에 일치하는 파일이 많더라도)을 새 줄의 변수로 출력하려면 어떻게 해야 합니까 ?

건배!

답변1

이는 쉘이 변수를 확장할 때 모든 개행 문자가 "접혀" 공백으로 변경되기 때문에 발생합니다. 따라서 out변수에 개행 문자가 포함되어 있으면 ${out}해당 개행 문자를 모두 공백으로 변경하세요. 그러나 "${out}"개행 문자는 보존됩니다.

답변2

filelist.csv파일과 정확히 일치하는 항목을 포함한 경우 find ... -print0 | grep -z -F -f filelist.csv | xargs -0r...와 같은 것을 사용할 수 있지만 해당 파일에 나열된 파일 이름의 일부(파일 이름 앞의 모든 문자 및 추가된 ".txt")와 일치하려는 것 같습니다. 이를 수행하는 가장 쉬운 방법은 정규식을 사용하는 것입니다.

당신은 그것을 사용할 수 있습니다프로세스 교체를 읽을 때 부분 파일 이름을 filelist.csv적절한 정규식으로 변환합니다.filelist.csvgrep

그런데 sed의 -i옵션을 사용하지 않는 한(이 특정 작업에서는 수행하지 마세요) 이 변환은 영구적이지 않으며 원본 filelist.csv 파일에는 영향을 주지 않고 입력 텍스트 스트림에만 영향을 미칩니다 grep -f.

또는 출력 find . -name '*.txt'grep. 이런 방식으로 grep에 표시된 입력은 로 끝나는 파일 이름에 대해 필터링되므로 .txt정규식 sed을 수정할 필요가 없습니다.

어쨌든 다음과 같이 시도해 보십시오.

먼저 이 실험을 위한 몇 가지 설정은 다음과 같습니다.

$ cat filelist.csv 
test
foo

$ touch test test.txt foo foo.txt footest footest.txt

$ ls -l
total 4
-rw-r--r-- 1 cas cas 10 Sep  8 04:01 filelist.csv
-rw-r--r-- 1 cas cas  0 Sep  8 04:01 foo
-rw-r--r-- 1 cas cas  0 Sep  8 04:01 footest
-rw-r--r-- 1 cas cas  0 Sep  8 04:01 footest.txt
-rw-r--r-- 1 cas cas  0 Sep  8 04:01 foo.txt
-rw-r--r-- 1 cas cas  0 Sep  8 04:01 test
-rw-r--r-- 1 cas cas  0 Sep  8 04:01 test.txt

그런 다음 bash 내장 함수를 사용하여 find 및 grep의 출력으로 호출된 배열을 mapfile채웁니다 .out

$ mapfile -d '' out < \
    <(find . -type f -print0 |
        grep -z -f <(sed -e 's/^\(.*\)/.*\1\.txt$/' filelist.csv)

또는:

$ mapfile -d '' out < \
    <(find . -type f -name '*.txt' -print0 |
        grep -z -f filelist.csv )

결과:

$ declare -p out
declare -a out=([0]="./foo.txt" [1]="./footest.txt" [2]="./test.txt")

$ ls -l "${out[@]}"
-rw-r--r-- 1 cas cas 0 Sep  8 04:01 ./footest.txt
-rw-r--r-- 1 cas cas 0 Sep  8 04:01 ./foo.txt
-rw-r--r-- 1 cas cas 0 Sep  8 04:01 ./test.txt

배열에는 , 및 , out만 포함되어 있지만foo.txtfootest.txttest.txt아니요 foo또는 test또는 footest.

$out그런데 다음을 사용하여 파일 이름을 반복할 수 있습니다.

for f in "${out[@]}"; do
  echo "$f"
  do-something-else-with "$f"
done

또는 값 대신 배열의 인덱스(0, 1, 2)를 반복합니다. 때로는 이것이 더 유용합니다. 예를 들어 동일한 인덱스를 가진 두 개 이상의 배열이 있고 일부에서 함께 사용하려는 경우입니다. 다른 목적으로 인덱스를 사용해야 하는 경우:

for i in "$!{out[@]}"; do
   echo "${out[$i]}"
done 

기억하다:

  1. 셸이 변수에 대한 glob을 토큰화 또는 ​​확장하거나 변수의 셸 메타 문자(예: or)에 대해 작동하지 않도록 하려면 변수(즉, "$var"단지 type이 아닌 type ) 를 큰따옴표로 묶으십시오. 이것은$var;&거의 언제나. 경험 법칙: 특정 경우에 큰따옴표 없이 변수를 사용해야 하는 이유를 정확히 모르는 경우 큰따옴표를 사용하십시오. 원래 질문에 대한 인용문 $out이나 직접적인 원인이 없습니다 .$filename

  2. 파일 이름에 공백이나 개행 문자와 같은 성가신 문자가 없을 것이라고 가정하지 마십시오. 이는 유닉스에서 파일 이름에 대해 완벽하게 유효한 문자이므로 스크립트에서 이를 처리해야 합니다. 사실은,오직경로/파일 이름에 나타날 수 없는 문자는 NUL입니다.

  3. 임의 파일 이름이나 알 수 없는 파일 이름 사이의 구분 기호로 항상 NUL을 사용하십시오. 이는 사용할 수 있는 유일한 구분 기호입니다.어느파일 이름.

  4. 그러나 많은 예외가 있습니다. 대부분의 경우 변수가 여러 값을 보유하도록 하려면 공백으로 구분된 문자열이나 유사한 "가짜/에뮬레이트 배열" 방법이 아닌 배열을 사용해야 합니다. 특히 값이 파일 이름이거나 값 중 하나에서 구분 기호가 유효한 문자인 경우에는 더욱 그렇습니다.

답변3

txt이름이 로 끝나고 중간 줄에 아무것도 포함되어 있는 모든 파일을 찾으려면 셸 filelist.csv에서 다음을 수행할 수 있습니다.zsh

print -rC1 -- **/*(${(j[|])~${(fb)"$(<filelist.csv)"}}*})*txt(ND)

아니면 한 번에 한 단계씩 세분화하세요.

csv_contents=$(<filelist.csv)
non_empty_lines_of_csv=(${(f)csv_contents})
lines_with_wildcards_excaped=(${(b)non_empty_lines_of_csv})
ored_patterns=${(j[|])lines_with_wildcards_excaped}
filename_pattern="*($ored_patterns)*txt"

print -rC1 -- **/$~filename_pattern(ND)

다음이 포함된 경우 filelist.csv:

???
foo bar
baz

그러면 다음과 같은 재귀 전역이 확장됩니다.

**/*(\?\?\?|foo bar|baz)*txt(ND)

귀하의 질문에 관해서는:

for filename in `cat filelist.csv`; do
out=`find /location*/time*/ -name *${filename}*txt`
echo ${out}
done
  • for var in `cmd`출력 라인을 반복하지 않고 출력을 가져오고 cmd후행 줄 바꿈 문자를 제거한 다음 그에 대해 분할+글로브를 수행합니다(zsh에서만 분할). 문자를 분할하고 (기본값은 공백, 탭 및 줄 바꿈) 확장합니다. 결과 단어의 와일드카드 문자입니다. 따라서 출력 인 경우 being 으로 한 번 반복 하지 않고 현재 디렉터리에서 . 로 시작하는 파일 이름을 반복 한 다음 로 시작하는 파일 이름을 반복합니다 .`cmd`cmd$IFScmda* b*vara* b*ab
  • 에서는 이러한 합계가 인용되지 않았기 -name *${filename}*.txt때문에 다시 쉘 와일드카드를 사용합니다. 따라서 그렇다면 쉘 은 일치하는 파일 목록으로 확장됩니다. 현재 디렉터리에 호출된 파일이 있으면 해당 파일은 가 됩니다 . 또한 로 수정하더라도 로 끝나는 파일 이름은 모두 반환 됩니다 .*${filename}${filename}abc*abc*txtxabcytxt-name xabcytxt-name "*${filename}*txt"$filename*-name '***txt'findtxt*
  • 에서 echo ${out}다시 $out따옴표가 없으면 분할+glob을 의미합니다(zsh 제외). 또한 echo다양한 구현의 다양한 특수한 경우에 원하는 대로 수행되지 않으므로 임의의 데이터를 출력하는 것을 피해야 합니다.

또한보십시오:

관련 정보