web-dev-qa-db-ja.com

ループでディレクトリを作成し、それらのディレクトリにファイルを移動する

N個のファイルがあるディレクトリを考えます。

ファイルをアルファベット順に並べ替えて保存できます。

ls > list

次に、同じディレクトリにn個のサブディレクトリを作成します。これは、

mkdir direc{1..n}

ここで、最初のmを移動するか、最初の5(1-5)ファイルをlistからdirec1に、次の5ファイル(6-10からdirec2など)に移動します。

これは皆さんにとって些細な作業かもしれませんが、現時点では実行できません。助けてください。

3
Polar.Ice
list=(*)     # an array containing all the current file and subdir names
nd=5         # number of new directories to create
((nf = (${#list[@]} / nd) + 1))  # number of files to move to each dir
                                 # add 1 to deal with Shell's integer arithmetic

# brace expansion doesn't work with variables due to order of expansions
echo mkdir $(printf "direc%d " $(seq $nd))

# move the files in chunks
# this is an exercise in arithmetic to get the right indices into the array
for (( i=1; i<=nd; i++ )); do
    echo mv "${list[@]:(i-1)*nf:nf}" "direc$i"
done

これをテストしたら、2つの「echo」コマンドを削除してください。

または、ディレクトリごとに固定数のファイルを使用する場合は、次のようにします。

list=(*)
nf=10
for ((d=1, i=0; i < ${#list[@]}; d++, i+=nf)); do
    echo mkdir "direc$d"
    echo mv "${list[@]:i:nf}" "direc$d"
done
3
glenn jackman
d=0; set ./somedirname                         #init dir counter; name
for f in ./*                                   #glob current dir
do   [ -f "$f" ] && set "$f" "$@" &&           #if "$f" is file and...
     [ "$d" -lt "$((d+=$#>5))" ]  || continue  #d<(d+($#>5)) or continue
     mkdir "$6$d" && mv "$@$d"    || !  break  #mk tgtdir and mv 5 files or
     shift 5                                   #break with error
done

上記のコマンドは、文字列をヘッドとテールに連結するシェルのarg配列の機能を利用しています。たとえば、関数を作成した場合:

fn(){ printf '<%s>\n' "42$@"; }

...そしてそれを次のように呼びました:

fn show me

...それは印刷します:

<42show>
<me>

... arg配列の最初または最後の要素に(それぞれ)を追加または追加できるため、事前/接辞文字列を引用符で囲むだけです。

Arg配列は、カウンターとしても機能するという点で、ここでも2つの役割を果たします。$#Shellパラメーターは、これまでにスタックした要素の正確な数を常に通知します。

しかし...これはステップバイステップです:

  1. d=0; set ./somedirname
    • $d変数は、新しいディレクトリが作成されるたびに1つずつ増加します。ここではゼロに初期化されています。
    • ./somedirnameはあなたが好きなものです。 ./プレフィックスは重要ですが、すべての操作を現在のディレクトリに確実にルート化するだけでなく、任意の種類の名前を指定することもできます(気が狂って改行を使用するか、開始する場合)ハイフンを使用すると安全に実行できますが、おそらくお勧めできません)。 argnameは常に./で始まるため、コマンドラインのオプションとして誤って解釈されることはありません。
  2. for f in ./*
    • これにより、現在のディレクトリの*に一致するすべての(存在する場合)のループが開始されます。繰り返しますが、各一致には接頭辞./が付いています。
  3. [ -f "$f" ]
    • 各反復の一致が間違いなく通常のファイルであることを確認します(または1つへのリンク)および...
  4. set "$f" "$@"
    • 一致するものをシェル配列の前に積み重ねます。このように、./somedirnameは常に配列の末尾にあります。
  5. [ "$d" -lt "$((d+=$#>5))" ]
    • $dに5つを超える配列要素がある場合は、"$@"に1を追加し、同時に結果の増分をテストします。
  6. || continue
    • [ -f "$f" ]set ...[ "$d" -lt...コマンドのいずれかがtrueを返さない場合、ループは次の反復に進み、残りのループを完了しようとはしません。これは両方とも効率的ですそして安全です。
  7. mkdir "$6$d"
    • continue句は、$# > 5./somedirname$6になり、$dの値が1だけ増加した場合にのみ、この時点まで到達できるようにするためです。したがって、一致して移動された5つのファイルの最初のグループについては、./somedirname1という名前のディレクトリが作成され、5番目のグループについては./somedirname5と続きます。重要なのは、このコマンド失敗ターゲットパス名を持つパス名がすでに存在する場合です。言い換えると、このコマンドは、同じ名前のディレクトリがすでに存在しないことが確実であれば、のみ成功です。
  8. mv "$@$d"

    • これにより、$dの値を最後の要素の末尾(ターゲットディレクトリ名)に付加しながら、配列が拡張されます。したがって、次のように展開されます。

    mv ./file5 ./file4 ./file3 ./file2 ./file1 ./somedirname1

    • ...まさにあなたがしたいことです。
  9. || ! break

    • 前の2つのコマンドのいずれかが何らかの理由で正常に完了しない場合、forループbreaks。 !は、breakの戻り値のブール値の逆を送信します。これは通常ゼロです。したがって、breakは1を返します。このようにして、前のコマンドでエラーが発生した場合、ループはfalseを返します。これは重要です--forループ--while/untilループとは異なり-テストを意味するのではなく、反復のみを意味します。これらの2つのコマンドの戻りを明示的にテストしないと、シェルは必ずしもエラーで停止するわけではありません。また、set -eは親シェルを完全に強制終了する可能性があります。むしろ、これは意味のある戻りを保証し、何かがうまくいかなくてもループが繰り返されないようにします。

    • 一見すると、これが停止する唯一の答えであるように見えます。たとえば、mkdir ./somedirnameがtrueを返さない場合、他のすべてはループを続けます(そして、エラーを繰り返す可能性があります。さらに悪いことに、現在のディレクトリ内のファイルを既存のディレクトリに移動し、場合によっては同じ名前の他のファイルの上に移動します)。ループ内で任意のファイル名を使用する場合は、alwaysソースファイルの存在をテストするandターゲットの存在を確認する必要があります。

  10. shift 5
    • これにより、シェルのarg配列の最初の5つの要素がshifts離れます。これにより、./somedirname$1に戻され、次の反復のために配列の状態がリセットされます。
2
mikeserv
_#!/bin/sh
d=1                # index of the directory
f=0                # number of files already copied into direc$d
for x in *; do     # iterate over the files in the current directory
  # Create the target directory if this is the first file to be copied there
  if [ $f -eq 0 ]; then mkdir "direc$d"; fi
  # Move the file
  mv -- "$x" "direc$d"
  f=$((f+1))
  # If we've moved 5 files, go on to the next directory
  if [ $f -eq 5 ]; then
    f=0 d=$((d+1))
  fi
done
_

役立つ参考資料:

このawkプログラムを使用すると、シェルコマンドを作成でき、疑わしい場合は、それらが正しいかどうかを事前に検査できます...

awk -v n=5 '{ printf "mv \"%s\" %s\n", $0, "direc" int((NR-1)/n)+1 }' list

出力に問題がない場合は、コマンド全体をshにパイプします。また、余分なファイル「リスト」を避けたい場合は、その場で作成できます。プログラム全体は次のようになります...

ls  |  awk -v n=5 '{ printf "mv \"%s\" %s\n", $0, "direc" int((NR-1)/n)+1 }'  |  sh

設定n = 5を変更すると、5以外の値を定義できます。

ターゲットディレクトリをその場で作成したい場合は、ここにバリアントがあります...

ls  |  awk -v n=5 '
         NR%n==1 { ++c ; dir="direc"c ; print "mkdir "dir }
         { printf "mv \"%s\" %s\n", $0, dir }
'  |  sh
1
Janis