Bash-부분 파일 이름 목록과 비교하여 파일의 디렉토리 확인


8

매일 클라이언트마다 파일을 디렉토리로받는 서버가 있습니다. 파일 이름은 다음과 같이 구성됩니다.

uuid_datestring_other-data

예를 들면 다음과 같습니다.

d6f60016-0011-49c4-8fca-e2b3496ad5a7_20160204_023-ERROR
  • uuid 표준 형식의 UUID입니다.
  • datestring의 출력입니다 date +%Y%m%d.
  • other-data 길이는 다양하지만 밑줄을 포함하지 않습니다.

다음 형식의 파일이 있습니다.

#
d6f60016-0011-49c4-8fca-e2b3496ad5a7    client1
d5873483-5b98-4895-ab09-9891d80a13da    client2
be0ed6a6-e73a-4f33-b755-47226ff22401    another_client
...

파일에 나열된 모든 uuid가 bash를 사용하여 디렉토리에 해당 파일이 있는지 확인해야합니다.

나는 이것을 멀리 얻었지만 if 문을 사용하여 잘못된 방향에서 오는 것처럼 느껴지고 소스 디렉토리의 파일을 반복해야한다고 생각합니다.

source_directory 및 uuid_list 변수는 스크립트에서 이전에 지정되었습니다.

# Check the entries in the file list

while read -r uuid name; do
# Ignore comment lines
   [[ $uuid = \#* ]] && continue
   if [[ -f "${source_directory}/${uuid}*" ]]
   then
      echo "File for ${name} has arrived"
   else
      echo "PANIC! - No File for ${name}"
   fi
done < "${uuid_list}"

내 목록의 파일이 디렉토리에 있는지 어떻게 확인해야합니까? bash 기능을 가능한 한 많이 사용하고 싶지만 필요한 경우 명령을 사용하는 것은 아닙니다.


파이썬? 그리고 서버 디렉토리는 "flat"입니까?
Jacob Vlijm

예, 평평하고 하위 디렉토리가 없습니다. 가능하다면 배쉬 만 고수하고 싶습니다.
Arronical

1
좋아, 나는 게시하지 않을 것이다.
Jacob Vlijm


나는 당신이 무엇을 잘못했는지 정말로 알지 못합니다. UUID 또는 파일을 반복해야합니다. 왜 하나의 루프가 다른 루프보다 낫습니까?
terdon

답변:


5

파일을 살펴보고, 이름에 포함 된 uuid에 대해 연관 배열을 작성하십시오 (uuid를 추출하기 위해 매개 변수 확장을 사용했습니다). 목록을 읽고 각 uuid에 대한 연관 배열을 확인하고 파일이 기록되었는지 여부를보고하십시오.

#!/bin/bash
uuid_list=...

declare -A file_for
for file in *_*_* ; do
    uuid=${file%%_*}
    file_for[$uuid]=1
done

while read -r uuid name ; do
    [[ $uuid = \#* ]] && continue
    if [[ ${file_for[$uuid]} ]] ; then
        echo "File for $name has arrived."
    else
        echo "File for $name missing!"
    fi
done < "$uuid_list"

1
훌륭하지만 (+1), 왜 ​​OP보다 더 나은가요? 동일한 기본 작업을 수행하지만 하나 대신 두 단계를 수행하는 것 같습니다.
terdon

1
@ terdon : 주요 차이점은 다음과 같습니다 .-) 와일드 카드 확장은 목록에서 줄을 읽을 때마다가 아니라 한 번만 수행됩니다.
choroba

예, 그것은 중요한 차이점입니다. 충분히 박람회 :)
terdon

감사합니다. 내 +1을 받았습니다. 파일을 보유한 디렉토리의 경로를 포함시키는 방법이 있습니까? cd스크립트 내 디렉토리에 들어갈 수 있다는 것을 알고 있지만 지식을 얻기 위해 궁금했습니다.
Arronical

@Arronical : 가능하지만 문자열에서 경로를 제거해야합니다 file=${file##*/}.
choroba

5

보다 "비싸고"간결한 접근 방식은 다음과 같습니다.

#!/bin/bash

## Read the UUIDs into the array 'uuids'. Using awk
## lets us both skip comments and only keep the UUID
mapfile -t uuids < <(awk '!/^\s*#/{print $1}' uuids.txt)

## Iterate over each UUID
for uuid in ${uuids[@]}; do
        ## Set the special array $_ (the positional parameters: $1, $2 etc)
        ## to the glob matching the UUID. This will be all file/directory
        ## names that start with this UUID.
        set -- "${source_directory}"/"${uuid}"*
        ## If no files matched the glob, no file named $1 will exist
        [[ -e "$1" ]] && echo "YES : $1" || echo  "PANIC $uuid" 
done

위의 꽤 있고 몇 개의 파일에 대한 벌금을 작동하지만, 그 속도는 UUID를의 수에 따라 달라되므로주의 매우 당신이 많은 처리해야하는 경우 느린. 이 경우 @choroba의 솔루션을 사용하거나 정말로 빠른 것을 위해 쉘을 피하고 호출하십시오 perl.

#!/bin/bash

source_directory="."
perl -lne 'BEGIN{
            opendir(D,"'"$source_directory"'"); 
            foreach(readdir(D)){ /((.+?)_.*)/; $f{$2}=$1; }
           } 
           s/\s.*//; $f{$_} ? print "YES: $f{$_}" : print "PANIC: $_"' uuids.txt

시간 차이를 설명하기 위해 20000 UUID가있는 파일에서 bash 접근 방식, choroba 및 펄을 테스트했으며 18001은 해당 파일 이름을 가졌습니다. 각 테스트는 스크립트의 출력을로 리디렉션하여 실행되었습니다 /dev/null.

  1. 내 배쉬 (~ 3.5 분)

    real   3m39.775s
    user   1m26.083s
    sys    2m13.400s
    
  2. 초로 바 (bash, ~ 0.7 sec)

    real   0m0.732s
    user   0m0.697s
    sys    0m0.037s
    
  3. 내 펄 (~ 0.1 초) :

    real   0m0.100s
    user   0m0.093s
    sys    0m0.013s
    

환상적으로 간결한 방법으로 +1하려면 파일을 포함하는 디렉토리에서 실행해야합니다. cd스크립트의 디렉토리에 들어갈 수 있지만 파일 경로를 검색에 포함시킬 수있는 방법이 있습니까?
Arronical

@Arronical은 업데이트 된 답변을 참조하십시오. ${source_directory}스크립트에서하는 것처럼 사용할 수 있습니다 .
terdon

또는 "$2"두 번째 인수로 스크립트에 사용 하고 전달하십시오.
Alexis

이것이 목적에 따라 충분히 빠르게 실행되는지 확인하십시오. 이와 같은 많은 파일 조회 대신 단일 디렉토리 스캔으로 수행하는 것이 더 빠릅니다.
Alexis

1
@alexis 네, 당신 말이 맞아요. 테스트를했는데 UUID / 파일 수가 증가하면 매우 느려집니다. 나는 훨씬 빠른 perl 접근법 (bash 스크립트 내에서 하나의 라이너로 실행할 수 있으므로 기술적으로 창의적으로 명명하면 여전히 bash)을 추가했습니다.
terdon

3

이것은 순수한 Bash (즉, 외부 명령 없음)이며 내가 생각할 수있는 가장 일치하는 접근 방식입니다.

그러나 성능 측면에서는 현재 가지고있는 것보다 훨씬 나쁘지 않습니다.

다음에서 각 줄을 읽습니다 path/to/file. 각 라인에 대해, 그것의 첫 번째 필드를 저장하는 것 $uuid및 패턴 매칭 파일이있는 경우 메시지를 인쇄 path/to/directory/$uuid*되어 있지 발견

#! /bin/bash
[ -z "$2" ] && printf 'Not enough arguments.\n' && exit

while read uuid; do
    [ ! -f "$2/$uuid"* ] && printf '%s missing in %s\n' "$uuid" "$2"
done <"$1"

로 전화하십시오 path/to/script path/to/file path/to/directory.

질문의 샘플 파일을 포함하는 테스트 디렉토리 계층에서 질문의 샘플 입력 파일을 사용한 샘플 출력 :

% tree
.
├── path
│   └── to
│       ├── directory
│       │   └── d6f60016-0011-49c4-8fca-e2b3496ad5a7_20160204_023-ERROR
│       └── file
└── script.sh

3 directories, 3 files
% ./script.sh path/to/file path/to/directory
d5873483-5b98-4895-ab09-9891d80a13da* missing in path/to/directory
be0ed6a6-e73a-4f33-b755-47226ff22401* missing in path/to/directory

3
unset IFS
set -f
set +f -- $(<uuid_file)
while  [ "${1+:}" ]
do     : < "$source_directory/$1"*  &&
       printf 'File for %s has arrived.\n' "$2"
       shift 2
done

여기서는 쉘이보고 할 오류보고에 대해 걱정하지 않아도됩니다. <존재하지 않는 파일 을 열려고하면 쉘이 불평합니다. 사실, 스크립트 $0와 오류가 발생한 줄 번호를 오류 출력에 추가합니다. 이것은 기본적으로 이미 제공되어있는 좋은 정보이므로 귀찮게하지 마십시오.

또한 파일을 한 줄씩 가져갈 필요가 없습니다. 엄청나게 느릴 수 있습니다. 이것은 공백으로 구분 된 인수 배열로 한 번에 전체를 확장하고 한 번에 두 개를 처리합니다. 데이터가 귀하의 예와 일치 $1하면 항상 귀하의 UUID $2가 될 것 $name입니다. 경우 bash귀하의 UUID로 경기를 열 수 있습니다 - 단 하나 같은 일치하는 항목이 - 후 printf발생합니다. 그렇지 않으면 쉘은 그렇지 않으며 쉘은 이유에 대해 stderr에 진단을 씁니다.


1
@kos-파일이 있습니까? 그렇지 않은 경우 의도 한대로 작동합니다. 공백으로 분할 unset IFS되도록합니다 $(cat <uuid_file). $IFS공백 만 포함되거나 설정되지 않은 쉘은 다르게 분할됩니다 . 모든 공백 시퀀스는 단일 필드 구분 기호로만 사용되므로 이러한 분할 확장에는 null 필드가 없습니다. 각 줄에 공백으로 구분되지 않은 두 개의 필드 만있는 한 작동해야한다고 생각합니다. 에서 bash, 어쨌든. set -f인용되지 않은 확장이 glob에 대해 해석되지 않도록하고 set + f를 사용하면 이후 globs가 보장됩니다.
mikeserv

@ kos-방금 수정했습니다. <>존재하지 않는 파일을 생성하기 때문에 사용해서는 안됩니다 . <내가 의도 한대로보고합니다. 그럼에도 불구하고 가능한 문제는-그리고 내가 <>처음에 잘못 사용하는 이유 는-독자가 없거나 파이프 라인이있는 char dev와 같은 파이프 파일 인 경우 중단됩니다. 오류 출력을보다 명시 적으로 처리하고 수행하면 피할 수 있습니다 [ -f "$dir/$1"* ]. 우리는 여기에서 uuid에 대해 이야기하고 있으므로 절대 하나 이상의 파일로 확장해서는 안됩니다. 실패한 파일 이름을 stderr에 어떻게보고합니까?
mikeserv

@ kos-실제로, 나는 ulimit를 사용하여 파일을 전혀 만들지 못하게하고 <>여전히 그렇게 사용할 수 있다고 가정합니다 ... <>리눅스에서 읽기 / 쓰기가 가능하기 때문에 glob가 디렉토리로 확장 될 수 있다면 더 좋습니다 실패와 말-디렉토리입니다.
mikeserv

@kos-오! 미안해-바보 일 뿐이야-당신은 두 경기가 있고, 그래서 옳은 일을하고 있습니다. 나는 두 개의 일치가있을 수 있다면 그 방법으로 오류가 발생한다는 것을 의미합니다. 완전히 의도적 이잖아 - 그리고 그것은 이다 모호한 가 야해하는 방식으로. 무슨 말인지 알 겠어? glob의 파일 이름을 지정하면 문제가 bash아닙니다. 여기서 특수 문자는 관련이 없습니다. 문제는 하나의 파일과 만 일치하는 경우 리디렉션 glob 만 허용 한다는 것입니다. 방향 재 지정을 참조 man bash하십시오.
mikeserv

1

내가 접근하는 방법은 먼저 파일에서 uuid를 가져온 다음 사용하는 것입니다. find

awk '{print $1}' listfile.txt  | while read fileName;do find /etc -name "$fileName*" -printf "%p FOUND\n" 2> /dev/null;done

준비를 위해

awk '{print $1}' listfile.txt  | \
    while read fileName;do \
    find /etc -name "$fileName*" -printf "%p FOUND\n" 2> /dev/null;
    done

/etc/passwd, group, fstab 및 THISDOESNTEXIST 파일 이름을 찾는 파일 목록이있는 예입니다 .

$ awk '{print $1}' listfile.txt  | while read fileName;do find /etc -name "$fileName*" -printf "%p FOUND\n" 2> /dev/null; done
/etc/pam.d/passwd FOUND
/etc/cron.daily/passwd FOUND
/etc/passwd FOUND
/etc/group FOUND
/etc/iproute2/group FOUND
/etc/fstab FOUND

디렉토리가 평평 -printf "%f\n"하다고 언급 했으므로 옵션을 사용하여 파일 이름 자체를 인쇄 할 수 있습니다

이것이하지 않는 것은 누락 된 파일을 나열하는 것입니다. find작은 단점은 파일을 찾지 못하면 파일과 일치하지 않을 때만 알려주지 않는다는 것입니다. 그러나 할 수있는 일은 출력을 확인하는 것입니다. 출력이 비어 있으면 파일이 없습니다.

awk '{print $1}' listfile.txt  | while read fileName;do RESULT="$(find /etc -name "$fileName*" -printf "%p\n" 2> /dev/null )"; [ -z "$RESULT"  ] && echo "$fileName not found" || echo "$fileName found"  ;done

더 읽기 쉬운 :

awk '{print $1}' listfile.txt  | \
   while read fileName;do \
   RESULT="$(find /etc -name "$fileName*" -printf "%p\n" 2> /dev/null )"; \
   [ -z "$RESULT"  ] && echo "$fileName not found" || \
   echo "$fileName found"  
   done

다음은 작은 스크립트로 수행되는 방법입니다.

skolodya@ubuntu:$ ./listfiles.sh                                               
passwd found
group found
fstab found
THISDONTEXIST not found

skolodya@ubuntu:$ cat listfiles.sh                                             
#!/bin/bash
awk '{print $1}' listfile.txt  | \
   while read fileName;do \
   RESULT="$(find /etc -name "$fileName*" -printf "%p\n" 2> /dev/null )"; \
   [ -z "$RESULT"  ] && echo "$fileName not found" || \
   echo "$fileName found"  
   done

stat플랫 디렉토리이기 때문에 대안으로 사용할 수 있지만 하위 디렉토리에 대해 다음 코드를 추가하기로 결정한 경우 다음 코드가 재귀 적으로 작동하지 않습니다.

$ awk '{print $1}' listfile.txt  | while read fileName;do  stat /etc/"$fileName"* 1> /dev/null ;done        
stat: cannot stat ‘/etc/THISDONTEXIST*’: No such file or directory

stat아이디어 를 가져 와서 실행하면 stat의 종료 코드를 파일의 존재 여부에 대한 표시로 사용할 수 있습니다. 효과적으로, 우리는 이것을하고 싶습니다 :

$ awk '{print $1}' listfile.txt  | while read fileName;do  if stat /etc/"$fileName"* &> /dev/null;then echo "$fileName found"; else echo "$fileName NOT found"; fi ;done

샘플 실행 :

skolodya@ubuntu:$ awk '{print $1}' listfile.txt  | \                                                         
> while read FILE; do                                                                                        
> if stat /etc/"$FILE" &> /dev/null  ;then                                                                   
> echo "$FILE found"                                                                                         
> else echo "$FILE NOT found"                                                                                
> fi                                                                                                         
> done
passwd found
group found
fstab found
THISDONTEXIST NOT found
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.