web-dev-qa-db-ja.com

上書きを防ぐために、移動したファイルの新しい名前を生成しますか?

同じ名前の既存のファイルがある場合、どうすればファイルの新しい名前を生成できますか?デスクトップ環境では、ファイル名の末尾に番号を追加して新しい名前が生成されますが、これをコマンドラインから実行するにはどうすればよいですか?

BusyboxでAndroidオペレーティングシステムを使用しています。

8
kyle k

POSIXシェルがあると仮定すると、次のことができます。

mv() {
        eval "DEST=\${$#}" #The destination is the last positional parameter
        if [ -e "$DEST" ] && ! [ -d "$DEST" ];then
                PREFIX=${DEST%.*}
                COUNT=1
                EXT=${DEST##*.}
                args= i=1
                while [ $i -lt $# ]; do args="$args \"\${$i}\"" i=$((i+1)); done
                DEST="$NAME-"$(printf "%03d" $COUNT)".$EXT"
                while [ -e "$DEST" ];do
                    COUNT=$((COUNT+1))
                    DEST="$NAME-"$(printf "%03d" $COUNT)".$EXT"
                done
                eval "command mv $args \"\$DEST\""
        else
                command mv "$@"
        fi
}

これの使い方

これは関数なので、~/.bashrcに保存し、通常のmvと同じように呼び出します。

これは何をするのか

  • 元のmv実行可能ファイルへのパスをMV変数に格納します
  • 呼び出された最後の引数を変数DESTに取得します
  • DESTが存在し、ディレクトリではない場合、この関数は、名前変更がファイルを破壊しようとしていると想定します
  • 次に、最終的な名前のプレフィックス(拡張子を示す、最後の.の前のすべて)、拡張子(最後の.の後のすべて)、カウント(存在する場合、プレフィックス内の何か最終-)。
  • 抽出されたカウントは、カウントが見つからなかった場合はゼロに設定され、それ以外の場合は前の手順で見つかったカウントに設定されます。
  • 現在のカウントがインクリメントされます
  • 次に、関数はすべての元の引数(スイッチ+ファイル名)から最後の引数を除いたもので自分自身を呼び出し、元の呼び出しの最後の引数の代わりに新しいファイル名を追加します。新しい名前は古い名前ですが、拡張子の前に3桁のカウンター(スタッフィングなし)が追加されています。
  • mv a.txt b.txtと言った場合、最初にmv a.txt b-001.txtを試行するため、この関数は再帰的です。この次のmv呼び出しも関数自体である必要があります。これは、b-001.txtも存在する場合、存在しない新しいファイル名が見つかるまでカウンターをインクリメントし続けるためです。
  • 最後の引数が存在しないか、ディレクトリである場合、元のmv実行可能ファイルが元の引数で呼び出されます。

警告

  • 既存のファイルのクローバーを繰り返し試行できる回数は、カウンターの長さによって異なります(この場合は999回)。ファイルシステムのiノード制限を含む桁数を選択して、ファイルを作成できる限りこれが機能するようにすることができます。
  • 名前がfoo-001.txtに似ているファイルをクローバーしようとすると、そのファイルはfoo-001-001.txtに移動されます。

メモ

  • 命名パターンを変更するには、printfステートメントの3を好きなように変更します。
  • このコードはテスト済みです
  • これは非常に単純であり、惨めに失敗するEdgeのケースがあると確信しています。見つかった場合は、修正してみてください。それまでの間、本番マシンでこれを試さないでください。
5
Joseph R.

私は通常、ツールmktempを使用して信頼できる一時ファイルを作成します。デフォルトではファイルの作成になりますが、-dスイッチを使用してディレクトリを作成することもできます。

現在のディレクトリ内のファイルに一時的な名前を作成する方法は次のとおりです。

$ mktemp somefile.XXXXX
somefile.kiDad

$ mktemp somefile.XXXX
somefile.MrSW

$ mktemp someotherfile.XXXXXXXXXXX
someotherfile.Um4aXKrt3lv

これにより、ファイルが作成されます。

参考文献

3
slm

これは、警告のないジョセフRのスクリプトの代替です!パス名に数値のサフィックスを追加し(パスはディレクトリまたはファイルにすることができます)、まだ存在していないサフィックス値が見つかるまでサフィックス値をインクリメントします。 logrotateなどの他のユーティリティも同様のパターンを使用しますが、既存のすべてのコピーをローテーションして、新しいコピーのサフィックスが常に「0」になるようにします。その意味ではローテーションではないので、dotmvと呼びます。 file.0最も古いコピーになることを覚えておいてください。

例えば:

dotmv somefile.txt

somefile.txtsomefile.txt.0の名前を変更します。ただし、後者が存在する場合は、somefile.txt.1などになります。複数のファイル(dotmv this that "the other thing"など)をリストすることができ、それらはすべてドット移動されます。

これはPOSIXに準拠していると思います。bashではset -o posixで実行されます(ただし、これは疑わしいテストです)。 Android(Jelly bean 4.2.1)シェルでもテストしましたが、そこで動作します。ただし、Androidでは、シバンを次のように変更する必要があります。 sh dotmvを指定または実行します。これは、ルート化されたデバイスがない限り、スクリプトを実行可能にする方法がないためです。Shebangを変更すると、exec dotmvを使用できるようになります。

#!/bin/sh
# On Android change that to /system/bin/sh.

# Validate arguments
if [ $# -lt 1 ]; then
    echo "A list of one or more paths is required."
    exit 1
fi

# Checks if a path exists and can be moved.
checkPath () {
    if [ ! -e "$1" ]; then
        echo "'$1' does not exist."
        return 1;
    fi
    if [ ! -w "$1" ]; then
        echo "Cannot move '$1', permission denied."
        return 1;
    fi
    return 0;
}

# Finds a new path with numerical suffix.
getName () {
    suf=0;
    while [ -e "$1.$suf" ]
        do let suf+=1
    done
    Dest=$1.$suf
}

# Loop through arguments -- use quotes to allow spaces in paths.
while (($#)); do
    Src=$1
    Dest=$1
    shift
    checkPath "$Src"
    if [ $? -eq 0 ]; then
        getName "$Src"
        mv "$Src" "$Dest"
    fi
done

うまくいけば、ここでのロジックは非常に単純です。これは、Python、C、またはファイルI/Oを使用したその他のチューリング完全な手続き型言語で実装できます。

2
goldilocks