상위 쉘에서 서브쉘의 값을 사용하는 방법

상위 쉘에서 서브쉘의 값을 사용하는 방법

저는 Linux 스크립팅에 익숙하지 않으며 이번이 처음으로 Linux 스크립팅을 사용하므로 다음과 같은 문제가 발생합니다.

암호:

while [ $pct -gt 80 ]; do
    flag=1;
    ls -tr | while read file; do
        if [[ $file =~ .+\.log[0-9]+ ]]; then
            printf "File deleted:\n";
            stat $file; 
            rm -r $file;
            flag=1;
            break;
        else
            flag=0;
        fi;
    done;

    if [ $flag -eq 0]; then
        break;
    fi;

    pct= # get the new pct;
done;

작업은 위 정규식으로 캡처된 특정 로그 파일을 가장 오래된 파일부터 삭제하는 것이므로 ls -tr을 사용합니다. while 루프를 사용하여 파일 목록을 반복하고 파일이 주어진 정규식과 일치하면 삭제합니다. 삭제할 때마다 사용 중인 애플리케이션 파일 시스템의 비율을 확인하고, 그 비율이 80%보다 큰 경우(외부 while 루프 조건에 표시됨) 프로세스를 반복합니다.

이제 파일을 삭제한 후에도 주어진 정규식 패턴의 파일이 남아 있지 않더라도 사용률이 80% 이하로 떨어지지 않으며 동일한 폴더에 남아 있는 다른 파일을 삭제할 수 없습니다. 따라서 무한루프에 빠지게 되므로, 이 경우에는 무한루프를 깨기 위해 플래그 변수를 사용하려고 했습니다. 그런데 파일을 반복하기 위해 파이프를 사용하고 있기 때문에 서브쉘이나 서브프로세스가 되어 부모 프로세스의 flags 변수를 사용할 수 없게 됩니다(서브쉘에 있는 플래그의 업데이트된 값은 부모 쉘에 반영되지 않습니다) ) (이것이 내가 (파이프에 대한 이 기사에서 읽은 것처럼)에서 수행하는 작업이므로 무한 루프가 절대 중단되지 않습니다. 누구든지 이 문제에 대한 해결책이나 대체 논리를 제안할 수 있습니까?

답변1

zsh대신 사용하십시오 bash:

while
  pct= # get the new pct

  (( pct > 80 )) &&
    oldest_log_file=( *.log<->(N.Om[1]) ) &&
    (( $#oldest_log_file ))
do
  print -r Removing $oldest_log_file
  rm -f -- $oldest_log_file
done

또는:

log_files_from_oldest_to_newest=( *.log<->(N.Om) )
while
  pct= # get the new pct

  (( pct > 80 )) &&
    (( $#log_files_from_oldest_to_newest ))
do
  print -r Removing $log_files_from_oldest_to_newest[1]
  rm -f -- $log_files_from_oldest_to_newest[1]
  shift 1 log_files_from_oldest_to_newest
done

또는:

zmodload zsh/stat
for log_file in *.log<->(N.Om); do
  pct= # get the new pct
  (( pct > 80 )) || break

  stat -F %FT%T%z -LH s -- $log_file || continue
  print -r Removing $log_file of size $s[size] last modified on $s[mtime]
  rm -f -- $log_file
done

답변2

ls 구문 분석은 매우 취약하며 실제로는 좋은 생각이 아닙니다. 그러나 파일 이름에 공백, 탭, 줄 바꿈(수정되지 않은 것으로 가정 $IFS)이나 전역 패턴 연산자(적어도 *, )가 포함되지 않고 and로 시작되지 않으며 유형도 포함되지 않을 것이라고 확신하는 경우?[-목차, 다음과 같이 할 수 있습니다.

#!/bin/bash
pct= # get the current pct
## are there any matching files?
files=( $(ls -tr *.log[0-9]* 2>/dev/null) );

## While pct is above the threshold and there is at least
## one file in the files array
while [[ $pct -gt 80 && ${#files[@]} -gt 0 ]]; do
    printf "Deleting file:\n%s\n" "$(stat -- "${files[0]}")"
    rm -- "${files[0]}"
    ## repopulate the files array
    files=( $(ls -tr *.log[0-9]* 2>/dev/null) );
    pct=85 # get the new pct;
done

여기서의 아이디어는 파일 이름을 배열에 저장하고 삭제할 때마다 배열을 다시 정의하는 것입니다. 그런 다음 "남은 파일이 있습니까?"와 "$pct가 80 미만입니까?"라는 두 가지 조건에서 루프를 실행하므로 두 조건 중 하나가 더 이상 참이 아닐 때 루프가 중지됩니다.

참고 1: 이는 와 같은 이름의 파일이 없다고 가정합니다 . 즉 , 문자열 뒤에 숫자가 포함된 모든 파일을 foo.log12bar보고 싶고 첫 번째 숫자 뒤에 숫자가 아닌 문자가 포함된 파일 이름을 피하고 싶지 않다고 가정합니다..log

노트 2: 처음에 언급했듯이 일반적이지 않은 이름을 가진 파일 이름에는 실패합니다. 구문 분석된 출력이 ls거의 항상 나쁜 생각인 이유는 여기를 참조하십시오 .

답변3

다음과 같이 시도해 보세요.

#!/bin/bash

dir="/path/to/dir"

# read the matching files into array "$files"
mapfile -d '' files < <(find "$dir" -maxdepth 1 -type f -regex '.*\.log[0-9]+' -print0)

for f in "${files[@]}"; do
  # get the current percentage used of the filesystem
  pct=$(df --output=pcent "$dir" | awk -F'[ %]' 'NR==2 {print $2}')

  [ "$pct" -lt 80 ] && break
  rm "$f"
done

파일 시스템의 현재 사용률이 80% 미만으로 떨어질 때까지 일치하는 파일을 한 번에 하나씩 계속 삭제합니다.또는일치하는 파일 이름이 더 이상 없을 때까지(둘 중 먼저 발생하는 날짜 기준)

여기에는 GNU df옵션이 필요합니다 --output.

GNU가 아닌 경우 df다음이 작동합니다.만약에장치 이름(파일 시스템 열)에는 공백이나 %문자가 포함되지 않습니다.

pct=$(df "$dir" | awk -F'[[:space:]]+|%' 'NR==2 {print $5}')

마운트 지점("마운트 위치" 열)에 공백이 포함되어 있지만 문자가 포함되어 있지 않은 경우 %다음과 같은 방법을 사용할 수 있습니다.

pct=$(df "$dir" | awk '
        NR==2 {
          for (i=NF; i > 0; i--) { if ($i ~ /%/) {break} };
          sub("%","",$i);
          print $i
        }')

(예, df출력에서 ​​데이터를 추출하는 것은 거의 같은 이유로 처음 생각보다 어렵습니다.ls를 구문 분석하는 것은 나쁜 생각입니다.)


타임스탬프별로 파일 이름을 정렬해야 하는 경우(가장 오래된 파일을 먼저 삭제할 수 있도록) 내 답변에 설명된 방법을 사용할 수 있습니다.가장 오래된 파일을 한 디렉토리에서 다른 디렉토리로 이동하는 쉘 스크립트. 다음과 같이 보일 것입니다:

mapfile -d '' files < <(
    find "$dir" -maxdepth 1 \
      -type f \
      -regex '.*\.log[0-9]+' \
      -printf '%C@\t%p\0' |
    sort -z -k1,1 -r -n |
    cut -z -f2
)

find이를 위해서는 , sort및 의 GNU 버전도 필요합니다 cut.

답변4

다른 훌륭한 답변에서 알 수 있듯이, 원하는 것을 달성하는 방법은 다양하므로 다른 구현을 포기하지 않겠습니다.

지금은 ls출력 처리의 함정을 무시하세요.

문제의 핵심은 변수 값을 루프 외부에 유지하는 방법인 것 같습니다. 그래서 나는 이것이 당신이 찾고 있는 것이라고 생각합니다:

flag=foo
while read file; do
  echo "do stuff with $file"
  flag=bar
done < <(ls -tr) # preferably do something else to get your list
echo "$flag"

관련 정보