N個のファイルがあるディレクトリを考えます。
ファイルをアルファベット順に並べ替えて保存できます。
ls > list
次に、同じディレクトリにn個のサブディレクトリを作成します。これは、
mkdir direc{1..n}
ここで、最初のmを移動するか、最初の5(1-5)ファイルをlist
からdirec1
に、次の5ファイル(6-10からdirec2
など)に移動します。
これは皆さんにとって些細な作業かもしれませんが、現時点では実行できません。助けてください。
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
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パラメーターは、これまでにスタックした要素の正確な数を常に通知します。
しかし...これはステップバイステップです:
d=0; set ./somedirname
$d
変数は、新しいディレクトリが作成されるたびに1つずつ増加します。ここではゼロに初期化されています。./somedirname
はあなたが好きなものです。 ./
プレフィックスは重要ですが、すべての操作を現在のディレクトリに確実にルート化するだけでなく、任意の種類の名前を指定することもできます(気が狂って改行を使用するか、開始する場合)ハイフンを使用すると安全に実行できますが、おそらくお勧めできません)。 argnameは常に./
で始まるため、コマンドラインのオプションとして誤って解釈されることはありません。for f in ./*
*
に一致するすべての(存在する場合)のループが開始されます。繰り返しますが、各一致には接頭辞./
が付いています。[ -f "$f" ]
set "$f" "$@"
./somedirname
は常に配列の末尾にあります。[ "$d" -lt "$((d+=$#>5))" ]
$d
に5つを超える配列要素がある場合は、"$@"
に1を追加し、同時に結果の増分をテストします。|| continue
[ -f "$f" ]
set ...
[ "$d" -lt...
コマンドのいずれかがtrueを返さない場合、ループは次の反復に進み、残りのループを完了しようとはしません。これは両方とも効率的ですそして安全です。mkdir "$6$d"
continue
句は、$# > 5
の./somedirname
が$6
になり、$d
の値が1だけ増加した場合にのみ、この時点まで到達できるようにするためです。したがって、一致して移動された5つのファイルの最初のグループについては、./somedirname1
という名前のディレクトリが作成され、5番目のグループについては./somedirname5
と続きます。重要なのは、このコマンド失敗ターゲットパス名を持つパス名がすでに存在する場合です。言い換えると、このコマンドは、同じ名前のディレクトリがすでに存在しないことが確実であれば、のみ成功です。mv "$@$d"
$d
の値を最後の要素の末尾(ターゲットディレクトリ名)に付加しながら、配列が拡張されます。したがって、次のように展開されます。mv ./file5 ./file4 ./file3 ./file2 ./file1 ./somedirname1
|| ! break
前の2つのコマンドのいずれかが何らかの理由で正常に完了しない場合、for
ループbreak
s。 !
は、break
の戻り値のブール値の逆を送信します。これは通常ゼロです。したがって、break
は1を返します。このようにして、前のコマンドでエラーが発生した場合、ループはfalseを返します。これは重要です--for
ループ--while/until
ループとは異なり-テストを意味するのではなく、反復のみを意味します。これらの2つのコマンドの戻りを明示的にテストしないと、シェルは必ずしもエラーで停止するわけではありません。また、set -e
は親シェルを完全に強制終了する可能性があります。むしろ、これは意味のある戻りを保証し、何かがうまくいかなくてもループが繰り返されないようにします。
一見すると、これが停止する唯一の答えであるように見えます。たとえば、mkdir ./somedirname
がtrueを返さない場合、他のすべてはループを続けます(そして、エラーを繰り返す可能性があります。さらに悪いことに、現在のディレクトリ内のファイルを既存のディレクトリに移動し、場合によっては同じ名前の他のファイルの上に移動します)。ループ内で任意のファイル名を使用する場合は、alwaysソースファイルの存在をテストするandターゲットの存在を確認する必要があります。
shift 5
shift
s離れます。これにより、./somedirname
が$1
に戻され、次の反復のために配列の状態がリセットされます。_#!/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
_
役立つ参考資料:
$((…))
[ … ]
_--
_の_mv -- "$x" …
_がなぜですか?この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