web-dev-qa-db-ja.com

ディレクトリを同じ名前のディレクトリに移動するにはどうすればよいですか?

私はいくつかのファイルを含むディレクトリfooを持っています:

.
└── foo
    ├── a.txt
    └── b.txt

同じ名前のディレクトリに移動したいと思います。

.
└── foo
    └── foo
        ├── a.txt
        └── b.txt

現在、一時ディレクトリbarを作成しています。foobarに移動し、後でbarfooに名前変更します。

mkdir bar
mv foo bar
mv bar foo

しかし、これは少し面倒な感じがします。barの名前を選択する必要がありますが、まだ使用されていません。

これを達成するためのよりエレガントで簡単な方法はありますか?それが重要であれば、私はmacOSを使用しています。

32
Stefan

現在のディレクトリに、まだ取得されていない名前で一時ディレクトリを安全に作成するには、次のように_mktemp -d_を使用できます。

_tmpdir=$(mktemp -d "$PWD"/tmp.XXXXXXXX)   # using ./tmp.XXXXXXXX would work too
_

_mktemp -d_コマンドは、パス名の末尾のX- esをランダムな英数字に置き換えて、指定されたパスにディレクトリを作成します。作成されたディレクトリのパス名を返し、この値をtmpdirに格納します。1

このtmpdir変数は、barを_"$tmpdir"_に置き換えて、すでに実行しているのと同じ手順に従うときに使用できます。

_mv foo "$tmpdir"
mv "$tmpdir" foo
unset tmpdir
_

最後の_unset tmpdir_は、変数を削除するだけです。


1通常、1つのすべきTMPDIR環境変数を、mktempを使用して一時ファイルまたはディレクトリを作成するディレクトリパスに設定できますが、macOSのユーティリティこれに関しては、他のBSDシステムの同じユーティリティとは微妙に異なる動作をするようで、ディレクトリをまったく別の場所に作成します。ただし、上記はmacOSで機能します。もう少し便利なtmpdir=$(TMPDIR=$PWD mktemp -d)またはtmpdir=$(TMPDIR=. mktemp -d)を使用しても、デフォルトの一時ディレクトリが別のパーティションとfooディレクトリに大量のデータが含まれていました(つまり、速度が遅くなります)。

21
Kusalananda

MacOSでは、Homebrewを使用してrenameコマンド(Perlスクリプト)をインストールできます。

brew install rename

次に、-p(la mkdir)を使用して必要なディレクトリを作成し、-Aを使用してプレフィックスを追加します。

% mkdir -p foo/bar; touch foo/{a,b}.txt foo/bar/c.txt
% rename -p -A foo/ foo/*
% tree foo
foo
└── foo
    ├── a.txt
    ├── b.txt
    └── bar
        └── c.txt

-nで実行して、名前を変更せずに変更を表示します(ドライラン):

% rename -p -A foo/ foo/* -n
'foo/a.txt' would be renamed to 'foo/foo/a.txt'
'foo/b.txt' would be renamed to 'foo/foo/b.txt'
'foo/bar' would be renamed to 'foo/foo/bar'

ドットファイルがある場合、単純な*がそれらを取得しないようにするには、renameで他のメソッドを使用します。

  • Bashでは、GLOBIGNOREを(誤って)使用して*を取得し、ドットファイルを照合します。

    $ GLOBIGNORE=.; rename -p -A foo/ foo/* -n
    'foo/.baz' would be renamed to 'foo/foo/.baz'
    'foo/a.txt' would be renamed to 'foo/foo/a.txt'
    'foo/b.txt' would be renamed to 'foo/foo/b.txt'
    'foo/bar' would be renamed to 'foo/foo/bar'
    
  • または、-print0findを使用し、-0renameを使用します。

    % find foo -mindepth 1 -maxdepth 1 -print0 | rename -0 -p -A foo/ -n
    Reading filenames from STDIN
    Splitting on NUL bytes
    'foo/b.txt' would be renamed to 'foo/foo/b.txt'
    'foo/a.txt' would be renamed to 'foo/foo/a.txt'
    'foo/bar' would be renamed to 'foo/foo/bar'
    'foo/.baz' would be renamed to 'foo/foo/.baz'
    
10
muru

内容が最大パラメーター制限を超えるのに十分でない限り(そして、「許容できる」エラーメッセージを気にしない限り)、これはこれ以上複雑にする必要はありません。

mkdir foo/foo
mv foo/* foo/foo

隠しファイルを処理するための修正:

mkdir foo/foo
mv foo/{.,}* foo/foo
10
bxm

私はその逆を提案します。ディレクトリは移動せず、そのコンテンツのみを移動します。

_.
└── foo
    ├── a.txt
    └── b.txt
_
_mkdir foo/foo
_
_.
└── foo
    ├── foo
    ├── a.txt
    └── b.txt
_
_cd foo
mv $(ls | grep -v '^foo$') foo
cd -
_
_.
└── foo
    └── foo
        ├── a.txt
        └── b.txt
_

あなたがbashを持っているなら、あなたもすることができます

_shopt -s extglob
cd foo
mv !(foo) foo
cd -
_

(説明されているように ここ )lsとgrepの実行を回避します。

このソリューションの利点は、それが本当に簡単であるということです。

短所(コメントで指摘されているとおり):

  • 隠しファイルを処理できません(_ls -A_は修正しますが、_ls -a_は修正しません)
  • スペースを含むファイル名は処理できません(引用符で修正できます:mv "$(ls | grep -v '^foo$')" foo
  • 改行やその他の特殊文字を含むファイル名は処理できません

ほとんどの欠点はbashトリックを使用することで対処できますが、クレイジーなファイル名を処理する必要がある場合は、他の回答で説明されているように、より堅牢なアプローチを使用することをお勧めします。

8
Adam Trhon

あなたはすでにそれをかなり釘付けしました。ナノ秒単位の現在の日付/時刻のターゲット名や複合サフィックスとしてのPIDなど、一時ディレクトリに別の名前を選択することもできますが、これはまだディレクトリが存在していないことを前提としています。

dir=foo                         # The directory we want to nest
now=$(date +'%s_%N')            # Seconds and nanoseconds
mkdir "$dir.$now.$$"            # Create a transient directory
mv -f "$dir" "$dir.$now.$$/"    # Move our directory inside transient directory
mv -f "$dir.$now.$$" "$dir"     # Rename the transient directory to the name with which we started

確実な堅牢なソリューションが必要な場合は、成功するまでmkdirをループします

now=$(date +'%s_%N')
while ! mkdir -m700 "$dir.$now.$$" 2>/dev/null
do
    sleep 0.$$
    now=$(date +'%s_%N')
done
6
roaima
mkdir foo/foo && mv foo/!(foo) foo/foo

ソースフォルダー(foo)があるディレクトリにcdする必要があります。

次に、上記のコマンドを実行します。同じ名前のと呼ばれるフォルダを作成し、親fooの内容を子fooディレクトリに移動します(子ディレクトリを除く、つまり!の指定を除く)。

子fooディレクトリがすでに存在する場合は、最初の部分を無視して、mvコマンドを実行するだけです。

mv foo/!(foo) foo/foo

MacOSでは、extglobオプションを設定する必要がある場合があります。

shopt -s extglob
6
masterl

上記の提案に加えて、rsyncをチェックアウトすることもできます。始める前に、常に--dry-runオプションなしで実行する前に、rsyncを使用してください。 --dry-runは、実際の生活で何が起こるかを教えてくれます。

rsyncには、ファイルのコピー/移動に役立つだけでなく、プロセスを高速化できる多くのオプションがあります。ここにあなたの仕事を終わらせる一つの方法があります。 --remove-source-filesoptionは、移動プロセス後にファイルをソースから削除します(実際には、コピーとそれに続く削除です)。 rsyncコマンドが最初にfooの内容を読み取り、fooという既存のディレクトリ内にディレクトリfooを作成してから、コピープロセスを開始することに注意してください。それはfooのソースファイルを削除することで終わります。注意:ディレクトリ名のスラッシュには特に注意してください。

多くの人が上記で指摘したように(シンボリックリンクなど)他の考慮事項がありますが、man rsyncは、必要なオプションを選択するのに役立ちます。ここでのメモ:ファイルの解決策を求めてきたので、これは機能します。これは、残された空のディレクトリを削除しません。追加の手順なしでこれを行う方法を私は知りませんので、誰かがこれに追加できる場合は、事前に感謝します!

mkdir foo
cd foo
touch aaa bbb ccc ddd
cd ..
rsync -av --remove-source-files foo foo/

a =アーカイブモード

v =詳細

2
Hopping Bunny

単にfooに触れないでください。同じ名前の問題は発生しません。代わりに、その中に同じ名前のディレクトリを作成し、そこにすべてを移動します。そのために$ _トリックを使用し、&&を1行にする:

_cd foo && mkdir $_ && mv * $_
_

これにより、無視できる無害なエラー(mv:rename foo to foo/foo:Invalid argument)がスローされます。

他の答えに対する利点:ディレクトリ名を一度入力するだけでよいので、タイプミスを間違えることはありません。


これを他の回答と組み合わせてさらに効果を上げることができます。

  • _mv {.,}* $__は隠しファイルを処理します(無視できるエラーが増える代わりに)
  • mv ./!(foo)はエラーメッセージを回避しますが、_shopt -s extglob_を設定した場合のみ
2
Tom

これは実際には一見単純ですが、他の誰もこの答えをまだ提供していないことに驚いています。そのため、ここでは例として以下を使用します...

$ mkdir foo
$ mkdir bar
$ mkdir ./bar/foo
$ touch ./bar/foo/file1 && touch ./bar/foo/file2 && touch ./bar/foo/file3
$ ls ./
bar/ foo/
$ ls ./bar/foo
file1 file2 file3

魔法の部分がやって来る

$ mv ./bar/foo ./foo/
$ ls ./foo/
foo/
$ ls ./foo/foo/
file1 file2 file3

なぜこれが機能するのですか?私は初心者なので、正確な答えはわかりませんが、末尾の/文字がフォルダでどのように処理されるかにかかっていることは理解しています。

そのmvコマンドをよく見てください

$ mv ./bar/foo ./foo/

ファイルのターゲットフォルダーが末尾の/なしで指定されているのに注意してください。ターゲットフォルダーの名前を共有する宛先は、末尾の/ 'を使用して指定されています。これはあなたの魔法の切符であり、あなたが注意を払っていないなら、あなたをロバに噛ませることができます。

現時点ではこれ以上の詳細な回答を提供することはできませんが、流暢に知識のある誰かが私の回答を自由に編集してくれるでしょう。

いずれにしても、lsコマンドを1回使用するだけで、中間フォルダーを作成しないので、これは最も単純で簡単な解決策のようです。

うまくいきますように、乾杯!

0
PowerLuser