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

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并重命名bar为foo:

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-print0和renamewith -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)