親シェルでサブシェルの値を使用する方法

親シェルでサブシェルの値を使用する方法

私は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未満ですか?」という2つの条件でループを実行するため、2つの条件の1つがもはや真にならないときにループが停止します。

注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%未満になるまで、一致するファイルを一度に1つずつ削除します。または一致するファイル名がなくなるまで(そのうち最初に発生する日付に基づいて)

これにはGNUdfオプションが必要です--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"

関連情報