쉘 스크립트: "파일이 사용되지 않은 경우" 조건

쉘 스크립트: "파일이 사용되지 않은 경우" 조건

스크립트를 통해 가상 머신 이미지 파일을 압축하려고 하는데 파일에 액세스하고 있지 않은지 확인하고 싶습니다. virt-manager가 이미지에 액세스하는 유일한 프로그램이어야 하므로 실행 중인지 확인할 수 있지만 이를 수행하는 더 좋은 방법이 있는지는 모르겠습니다. 또한 파일이 압축될 준비가 될 때까지 스크립트가 계속 시도되기를 원합니다. 나도 무엇을 해야할지 모르겠습니다.

#Check if virt-manager is running
if pgrep "virt-manager" > /dev/null
then
    #re-run script until success
else
    gzip -k < /home/brady/.vms/windows10/hdd.img > /media/backup/vms/windows10/hdd.$(date +"%F.%T).img.gz

답변1

lsof명령은 파일이 사용 중인지 여부를 알려줍니다. 자주 확인하도록 while루프 에 넣을 수 있습니다 .sleep

예를 들어:

창 1에서는 다음을 실행할 수 있습니다.sleep 10000 > /tmp/x

창 2에서 다음 스크립트를 실행합니다.

#!/bin/bash

FILE=/tmp/x

while [ -n "$(lsof "$FILE")" ]
do
  sleep 1
done

echo "File $FILE not in use"

control-C이제 중단 버튼을 누르면 sleep약 1초 동안 "파일이 사용되지 않음"이라는 응답이 표시됩니다.

답변2

inotify 도구가 설치된 Linux에서는 다음을 수행할 수 있습니다.

#! /bin/zsh -
file=${1?}

# if it's a symlink, we want the real file, readlink also tells us
# if the file is accessible
file=$(readlink -e -- "$file") || exit

# start inotifywait as a coproc so we can terminate it after we're
# done:
coproc LC_ALL=C inotifywait -me close --format . -- "$file" 2>&1

# Now wait for the "Watches established." messaged. First, that allows us
# to verify inotifywait started properly, and that also avoids the race
# condition where the last file user is gone after our fuser check but
# before the watch is in place
read <&p && read <&p && [ "$REPLY" = "Watches established." ] || exit

# Now watch CLOSE events until the file has no more user:
while fuser -s "$file" && read <&p; do continue; done
printf '"%s" is no longer used, renaming it to prevent new access\n' "$file"
kill %
ret=0
if mv -- "$file" "$file.moved-away"; then
  printf 'and now compressing it\n'
  pixz -t < "$file.moved-away" > "$file.xz" || ret=$?
  mv -- "$file.moved-away" "$file" || ret=$? # move back
else
  ret=$?
fi
exit "$ret"

를 사용하면 inotifywait파일의 fd가 닫힐 때마다 알림을 받게 됩니다. 이는 파일을 자주 확인할 필요가 없으며 마지막 사용자가 파일을 닫자마자 압축을 시작할 수 있음을 의미합니다.

내 테스트 결과, 초기 우려와는 달리 이는 매핑된 파일에도 적용됩니다. 이러한 경우 이벤트는 CLOSE마지막에 생성되지 않고 close()맨 끝 munmap()(파일이 완전히 해제될 때)에 생성되기 때문입니다.

fuser -s는 것 보다 더 나을 것입니다 lsof.fuser표준 UNIX 명령( -s표준 옵션은 아니지만 Linux에서 사용 가능한 버전은 이를 지원합니다).

압축하기 전에 추가 액세스를 방지하기 위해 파일을 이동합니다.

우리는 gzip보다 더 나은 압축률을 제공하고 더 중요한 것은 압축된 파일이 무작위로 액세스되기 때문에 ( 최신 버전 도 pixz멀티스레딩을 지원하지만 멀티스레드 버전을 사용합니다. 콘텐츠를 마운트하거나 시작할 수 있습니다. 전체 이미지의 압축을 풀지 않고 nbdkit을 사용하는 VM에서).xzxz

또는 장치 백엔드 로 사용되는 lsof파일은 감지 fuser되지 않습니다 . 루프 장치의 경우 이를 사용하여 파일이 이런 방식으로 사용되고 있는지 확인할 수 있습니다. 예를 들어, 파일이 이동된 후에 다음 루프를 삽입할 수 있습니다.loopmtdlosetup -j "$file"

while [ -n "$(losetup -j "$file.moved-away")" ]; do
  sleep 1
done

답변3

lsof작업에 적합한 도구이지만 기본적으로 모든 PID를 확인하므로 느리고 CPU를 많이 사용합니다. 다행히도 작업 속도를 높일 수 있는 방법이 있습니다.

그런데 virt-manager이는 가상 머신 디스크 이미지 파일/장치를 열어두는 프로세스가 아닙니다. 이는 바이너리 중 하나입니다 qemu.qemu-system-x86_64

 

특정 프로세스만 파일을 열 가능성이 있고 해당 프로세스의 PID를 알고 있거나 얻을 수 있는 경우 해당 프로세스를 쉼표로 구분된 목록으로 제공할 수 있습니다 lsof -p.

pids=$(pgrep qemu-system | paste -sd,)
[ -n "$pids" ] && lsof -p "$pids" | grep -i filename

더 좋은 점은 options 를 사용하여 프로세스 lsof이름을 지정할 수 있다는 것입니다 -c. -c프로세스 이름이 정확히 일치할 필요는 없으며 패턴(최대 길이 15자)이 필요합니다. 필요한 경우 명령줄에서 이를 여러 번 사용할 수 있습니다 -c. man lsof자세한 내용은 lsof FAQ를 참조하세요 .

15자를 초과하여 사용하면 다음과 같은 오류 메시지가 표시됩니다.

# lsof -c qemu-system-x86_64
lsof: "-c qemu-system-x86_64" length (18) > what system provides (15)

어쨌든 예를 들면 다음과 같습니다.

# lsof -c qemu-system | grep -i FreeBSD-10.2-RELEASE-amd64.qcow2
qemu-syst 4770 libvirt-qemu   20u      REG                8,3 1837236224 403730954 /var/lib/libvirt/images/FreeBSD-10.2-RELEASE-amd64.qcow2

루프 에서는 while다음과 같습니다.

pname='qemu-system'
fname='FreeBSD-10.2-RELEASE-amd64.qcow2'

while lsof -c "$pname" | grep -qi "$fname" ; do
  sleep 0.1   # don't need to sleep for as long between checks
              # but if you're not impatient, leave it at 1 second
              # rather than 0.1.
done

echo "$fname is not in use"

이는 가상 머신에 원시 파티션을 사용하는 경우에도 작동합니다(예: fname='/dev/sda5'위의 스크립트 조각에서).

파일 기반 이미지 대신 ZFS ZVOL 또는 LVM LV 또는 이와 유사한 것을 사용하면 상황이 좀 더 복잡해집니다. lsof실제 블록 장치 이름은 기호 링크를 해석한 후에 표시되므로 기호 링크도 해석해야 합니다(예: readlink -fgrep 사용).

freedos예를 들어 ZFS의 경우 풀에서 ZVOL을 호출합니다 volumes.

# fname=$(readlink --n f /dev/zvol/volumes/freedos)

# echo "$fname"
/dev/zd32

이름이 LV인 LVM의 경우 centos7:

# fname=$(readlink -n -f /dev/mapper/centos7)

# echo $fname
/dev/dm-1

참고: /dev/vg/centos7대체 /dev/mapper/centos7도 가능합니다.


find처음에는 이에 대한 답변으로 방법을 작성하기 시작했지만 이 lsof -c방법이 더 낫다는 것을 깨달았습니다. 나는 또 다른 상당히 빠른 대안을 문서화하기 위해 여기에 남겨 둡니다.

find -lnamelsofPID 옵션 없이 실행하는 것보다 시스템에서 더 빠르고 가볍습니다 -p.

예를 들어

# sleep 10000 > /tmp/foo &
[1] 31077
# find /proc/[0-9]*/fd/ -lname '/tmp/foo'
/proc/31077/fd/1

관련 정보