如何将目录移动到同名目录中?

Ste*_*fan 32 directory mv macos

我有一个foo包含多个文件的目录:

.
??? foo
    ??? a.txt
    ??? b.txt
Run Code Online (Sandbox Code Playgroud)

我想将它移动到一个同名的目录中:

.
??? foo
    ??? foo
        ??? a.txt
        ??? b.txt
Run Code Online (Sandbox Code Playgroud)

我目前正在创建一个临时目录bar,然后foo进入bar并重命名barfoo

mkdir bar
mv foo bar
mv bar foo
Run Code Online (Sandbox Code Playgroud)

但这感觉有点麻烦,我必须为bar尚未使用的名称选择一个名称。

有没有更优雅或更直接的方法来实现这一目标?如果这很重要,我在 macOS 上。

Kus*_*nda 22

要在当前目录中安全地创建一个临时目录,其名称尚未被采用,您可以mktemp -d像这样使用:

tmpdir=$(mktemp -d "$PWD"/tmp.XXXXXXXX)   # using ./tmp.XXXXXXXX would work too
Run Code Online (Sandbox Code Playgroud)

mktemp -d命令将在给定路径上创建一个目录,路径X名末尾的-es 将替换为随机字母数字字符。它将返回创建的目录的路径名,我们将此值存储在tmpdir. 1

tmpdir然后可以在执行您已经在执行的相同过程时使用此变量,并bar替换为"$tmpdir"

mv foo "$tmpdir"
mv "$tmpdir" foo
unset tmpdir
Run Code Online (Sandbox Code Playgroud)

unset tmpdir在年底只是删除了变量。


1 通常,人们应该能够将TMPDIR环境变量设置为想要使用 来创建临时文件或目录的目录路径mktemp,但是 macOS 上的实用程序在这方面的工作方式似乎与其他 BSD 系统上的相同实用程序略有不同,并将在完全不同的位置创建目录。然而,上述内容适用于 macOS。如果默认临时目录在另一个分区上并且该目录包含大量数据(即它会很慢),则使用稍微方便一些tmpdir=$(TMPDIR=$PWD mktemp -d)甚至tmpdir=$(TMPDIR=. mktemp -d)只会在 macOS上出现问题foo

  • @Stefan 是的,这是一个问题。请参阅我关于该主题的问题:[macOS 上的 mktemp 不支持 $TMPDIR](//unix.stackexchange.com/q/555058) (2认同)

mur*_*uru 10

在 macOS 上,您可以rename使用 Homebrew安装命令(Perl 脚本):

brew install rename
Run Code Online (Sandbox Code Playgroud)

然后使用-p(a 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
Run Code Online (Sandbox Code Playgroud)

运行-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'
Run Code Online (Sandbox Code Playgroud)

如果您有 dot 文件,以便简单的*不会拿起它们,然后使用其他方法rename

  • 使用bash,(MIS)的使用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'
    
    Run Code Online (Sandbox Code Playgroud)
  • 或使用findwith-print0renamewith -0

    % 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'
    
    Run Code Online (Sandbox Code Playgroud)


bxm*_*bxm 10

只要内容不足以超过最大参数限制(并且您不介意“可接受”的错误消息),那么它不需要比这更复杂:

mkdir foo/foo
mv foo/* foo/foo
Run Code Online (Sandbox Code Playgroud)

处理隐藏文件的修正:

mkdir foo/foo
mv foo/{.,}* foo/foo
Run Code Online (Sandbox Code Playgroud)


Ada*_*hon 8

我建议反过来。不要移动目录,而只移动它的内容:

.
??? foo
    ??? a.txt
    ??? b.txt
Run Code Online (Sandbox Code Playgroud)
mkdir foo/foo
Run Code Online (Sandbox Code Playgroud)
.
??? foo
    ??? foo
    ??? a.txt
    ??? b.txt
Run Code Online (Sandbox Code Playgroud)
cd foo
mv $(ls | grep -v '^foo$') foo
cd -
Run Code Online (Sandbox Code Playgroud)
.
??? foo
    ??? foo
        ??? a.txt
        ??? b.txt
Run Code Online (Sandbox Code Playgroud)

如果你有 bash,你也可以这样做

shopt -s extglob
cd foo
mv !(foo) foo
cd -
Run Code Online (Sandbox Code Playgroud)

(如所描述的在这里),以避免运行LS和grep。

这个解决方案的优点是它非常简单。

缺点(如评论中所指出的):

  • 无法处理隐藏文件(ls -A修复它,但不是ls -a
  • 无法处理包含空格的文件名(可以通过引号修复: mv "$(ls | grep -v '^foo$')" foo
  • 无法处理包含换行符和其他特殊字符的文件名

大多数缺点可以通过使用一些 bash 技巧来解决,但如果必须处理疯狂的文件名,最好使用更强大的方法,如其他答案中所述。

  • 正如已经指出的那样,这将错过任何隐藏文件,并且也会在任何带有空格的文件名上失败。[解析 `ls` 从来都不是一个好主意](https://mywiki.wooledge.org/ParsingLs)。 (8认同)

roa*_*ima 6

你已经基本搞定了。您可以为瞬态目录选择不同的名称,例如具有当前日期/时间(以纳秒为单位)和我们的 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
Run Code Online (Sandbox Code Playgroud)

如果你想要一个有保证的强大解决方案,我会循环mkdir直到它成功

now=$(date +'%s_%N')
while ! mkdir -m700 "$dir.$now.$$" 2>/dev/null
do
    sleep 0.$$
    now=$(date +'%s_%N')
done
Run Code Online (Sandbox Code Playgroud)

  • @Stefan 无法直接通过 inode 编号访问文件;如果这是可能的,它将完全破坏安全模型。如果在 `foo` 的父目录中创建另一个临时目录很复杂,您可以就地重构 `foo` 目录: `mkdir foo/foo; mv foo/* foo/foo`(对于最后一部分,您可以忽略错误,或使用扩展的通配符跳过 `foo/foo` 子目录本身)。 (2认同)
  • @Stefan 您的“find”通过名称引用(使用)目录,其中的标识标准是 inode 编号。 (2认同)

小智 6

mkdir foo/foo && mv foo/!(foo) foo/foo
Run Code Online (Sandbox Code Playgroud)

您需要 cd 进入源文件夹 (foo) 所在的目录。

然后运行上面的命令。它将创建一个同名文件夹,并将父 foo 的内容移动到子 foo 目录(子目录除外,因此使用 ! 指定)。

如果子 foo 目录已经存在,您可以忽略第一部分并运行 mv 命令:

mv foo/!(foo) foo/foo
Run Code Online (Sandbox Code Playgroud)

在 MacOS 中,您可能需要设置 extglob 选项:

shopt -s extglob
Run Code Online (Sandbox Code Playgroud)