名前の一部でファイルの範囲を選択する方法

名前の一部でファイルの範囲を選択する方法

コピーや移動などに使用するファイル名の一部を使用してファイルの範囲を選択したい。

ランダムな例のリスト:

  1. アルファ1a
  2. アルファ2aa
  3. アルファ3abc
  4. ブラボーA1
  5. ブラボーB2bb
  6. チャーリー
  7. デルタA1fdfd
  8. デルタA2ダファ
  9. デルタA2222
  10. デルタC1
  11. ...(他のファイルも多数)

alpha2* から deltaA* まで、リストの 2 ~ 9 を選択するコマンドを実行したいと思います。この方法により、名前に基づいて範囲を選択でき、実際にファイルがいくつあるかを気にする必要がなくなり、余分なものも取得されません。

答え1

start=alpha2*
end=deltaA*

for file in *
do
    if [[ $file > $start && ( $file < $end || $file == $end ) ]]; then
        echo $file
    fi
done

echo の代わりに、ファイルを配列に保存することもできます。

array+=($file)

そして、配列を使用してファイルをコピー、移動したり、単に for ループ内でコマンドを実行したりします。

答え2

シェルプログラミングは辞書式の比較があまり得意ではありません。bash、ksh、zshでは、< 条件演算子2 つの文字列を比較します (POSIX sh には数値比較のみがあります)。

run_in_range () {
  start_at="$1"; end_before="$2"; command="$3"; shift 3
  for item do
    if [[ ! ($x < $start_at) && ($x < $end_before) ]]; then
      "$command" "$item"
    fi
  done
}
run_in_range alpha2 deltaB some_command *

最初に除外された項目を 2 番目の引数として使用したことに注意してください。は、deltaBで始まるすべての文字列の後に続く最小の文字列ですdeltaA。引数として渡す場合はdeltaA、この方法で行うことができます。

run_in_inclusive_range () {
  start_at="$1"; end_on_prefix="$2"; command="$3"; shift 3
  for item do
    if [[ ! ($x < $start_at) && ($x < $end_on_prefix || $x == "$end_on_prefix"*) ]]; then
      "$command" "$item"
    fi
  done
}
run_in_inclusive_range alpha2 deltaA some_command *

このコードはワイルドカードの順序に依存しません。つまり、項目のリストがソートされていなくても機能します。これにより、コードはより堅牢になりますが、リスト全体を調べる必要があるため、若干遅くなります。

POSIX 機能のみに依存する代替アプローチを次に示します。これは、sort文字列の比較を行うために を使用します。これは、項目に改行がないことを前提としています。この関数は、 と globbing のデフォルト設定を復元しますIFS(周囲の設定を復元することは可能ですが、非常に困難です)。

run_in_range () {
  IFS='
'; set -f
  start_at="$1"; end_before="$2"; command="$3"; shift 3
  set $({
          printf '%s1\n' "$@"
          printf '%s0\n' "$start_at" "$end_before"
        } | sort | sed '/0$/,/0$/ s/1$//p')
  unset IFS; set +f
  for item do
    "$command" "$item"
  done
}

説明:

  1. 1追加された項目と追加された境界を含むリストを作成します0
  2. リストを並べ替えます。
  3. 2 つの境界線 ( で終わる行0) の間の行を印刷します。ただし、 で終わる行1(つまり項目) のみを印刷し、末尾の は印刷しません1

答え3

この問題は見た目よりも簡単に解決できるように思えます。

1 行に 1 つのファイルというリストを取得する方法があると仮定します。
現在のディレクトリの場合:

list="$(printf '%s\n' *)"

リストに制限を追加して並べ替えるだけです。

nl='
'
lim1="alpha2"
lim2="deltaA"

echo -- "$list""$nl""$lim1""$nl""$lim2" | sort

次に、lim1 で印刷を開始し (lim1 と lim2 が既存のファイル名と一致しないように注意します)、lim2 で停止します。

また、簡単にするために、印刷するファイルの直前にある最短の文字列と、選択する必要がある最後のファイルの直後にある最短の文字列を選択します。

便利なのは排他的なリスト(制限を含まないリスト)です
が、両方のソリューションが提供されています。

基本的な価値観:

list="$(printf '%s\n' *)"

lim1=alpha2
lim2=deltaA

包括的なリストのための awk を使用したソリューション:

echo "awk----------------inclusive"
echo "$list""$nl""$lim1""$nl""$lim2" | sort |
    awk -v lim1="$lim1" -v lim2="$lim2" '$0==lim1,$0==lim2{print}'

排他的リストに対する awk を使用したソリューション:

echo "awk----------------exclusive"
echo "$list""$nl""$lim1""$nl""$lim2" | sort |
    awk -v lim1="$lim1" -v lim2="$lim2" '$0==lim1{p=1;next};$0==lim2{p=0};p'

包括的なリストのためのシェルを使用したソリューション:

echo "shell---------------inclusive"
show=0
echo "$list""$nl""$lim1""$nl""$lim2" | sort |
    while IFS="$nl" read -r line; do
        [ "$line" = "$lim1" ] && show=1
        [ "$show" = "1"     ] && printf '%s\n' "$line"
        [ "$line" = "$lim2" ] && show=0
    done

そしてシェル専用のソリューション:

show=0
echo "$list""$nl""$lim1""$nl""$lim2" | sort |
    while read -r line; do
        [ "$line" = "$lim2" ] && show=0
        [ "$show" = "1"     ] && printf '%s\n' "$line"
        [ "$line" = "$lim1" ] && show=1
    done

関連情報