如何将文件从X子目录移动到Y子目录

3
我有一个主目录X,里面有许多子目录如A、B、C、D。我有一个主目录Y,里面也有许多同名子目录,如X主目录下的A、B、C、D。
现在我需要将X主目录下的文件移动到Y主目录相应的子目录中。
例如:
X主目录中,子目录A有100个文件,
子目录B有1个文件,
子目录C有5个文件,
子目录D有50个文件……
我的光标应该在主目录X处,从那里我需要将所有子目录文件移动到Y主目录中具有相同名称的子目录(A、B、C、D)中。
我要如何在SHELL脚本(KSH)或Unix中完成这项任务?
3个回答

1
注意:根据需要进行了根本修订,以提出更简单的解决方案。一行式解决方案(更复杂)在底部。
一个符合POSIX标准的解决方案:
#!/bin/sh

# Specify source and target directories (example values)
dirFrom='/tmp/from'
dirTo='/tmp/to'

# Use globbing to find all subdirectories - note the trailing '/'
# to ensure that only directories match.
for subdir in "$dirFrom"/*/; do

  # Extract the mere name.
  # Note that using ${subdir##*/} is NOT an option here, because $subdir ends in '/'.
  name=$(basename -- "$subdir")

  # Make sure a subdirectory of the same
  # name exists in the target dir.
  mkdir -p "$dirTo/$name"

  # Move all items in the source subdir. to the analogous target subdir.
  mv "$subdir"* "$dirTo/$name/"

done  

上述内容同样适用于ksh,以及其他符合POSIX标准的shell,如bashzsh
这种解决方案存在两个潜在问题
  • 隐藏项目(名称以.开头的项目)将被忽略
  • 如果没有子目录或其中任何一个是空的,脚本将会中断,因为像*这样不匹配任何内容的通配符将被保留,导致不存在的路径被传递给mv

更强大的Bash版本

ksh不同,Bash有选项可解决这两个问题:
  • shopt -s dotglob使得在执行通配符操作时,包含隐藏项目。
  • shopt -s nullglob使得不匹配任何内容的通配符扩展为空字符串。
#!/usr/bin/env bash

# Specify source and target directories (example values)
dirFrom='/tmp/from'
dirTo='/tmp/to'

shopt -s dotglob   # include hidden items when matching `*`
shopt -s nullglob  # expand patterns that don't match anything to the emtpy string

# Use globbing to find all subdirectories - note the trailing '/'
# to ensure that only directories match.
for subdir in "$dirFrom"/*/; do

  # Extract the mere name.
  # Note that using ${subdir##*/} is NOT an option here, because $subdir ends in '/'.
  name=$(basename -- "$subdir")

  # Make sure a subdirectory of the same
  # name exists in the target dir.
  mkdir -p "$dirTo/$name"

  # Collect the paths of the items in the subdir. in 
  # an array, so we can test up front whether anything matched.
  itms=( "$subdir"* )

  # Move all items in the source subdir. to the analogous target subdir,
  # but only if the subdir. contains at least 1 item.
  [[ ${#itms[@]} -gt 0 ]] && mv "${itms[@]}" "$dirTo/$name/"

done
  • 注意,仅使用shopt -s nullglob是不够的——我们仍然需要先在数组中收集globbing匹配项,以便确定是否有任何匹配项。
    • 虽然我们可以使用2>/dev/null来让mv在没有匹配项时静默失败,但这并不可取,因为它可能掩盖了真正的错误条件。

如果你对一行代码感兴趣,这里有基于find命令的代码;然而,它们相当复杂。
# Note: Both solutions below ignore symlinks to directories as subdirectories.

# [POSIX-compliant] Excluding hidden items.
# Note how `\! -name '.*'` is explicitly added to the `find` command to ensure that no hidden subdirs. are matched, so as to
# to match the shell globbing behavior of excluding hidden items.
find "$dirFrom" -mindepth 1 -maxdepth 1 -type d \! -name '.*' -exec sh -c \
  'f=$1 t=$2/${1##*/}; set -- "$f"/*; [ -e "$1" ] && mkdir -p "$t" && mv "$@" "$t"' \
  - {} "$dirTo" \;

# [Bash] Including hidden items.
find "$dirFrom" -mindepth 1 -maxdepth 1 -type d -exec bash -O dotglob -c \
  'f=$1 t=$2/${1##*/}; set -- "$f"/*; [[ -e $1 ]] && mkdir -p "$t" && mv "$@" "$t"' \
  - {} "$dirTo" \;

0

这里有一个应该可以工作的shell脚本。我还没有时间检查它,请您检查并确认。

#!/bin/bash

##################################################################
#
#   Please make sure this script is run from source     
#   directory. Also you need to have read, write and    
#   execute permission for this directory.
#
#   If the subdirectories with same names are not present   
#   then those will be created directly.
##################################################################

# Replace name of source directory by "X"
sourcedir="X"

# Replace name of destination directory with complete path by "Y"
destdir="/home/user/Y"

# Copying names of all subdirectories in a temporary text file
ls -Rl | grep "[a-z]:" | sed 's/://' | cut -c 2- > temp.txt

# Moving files directory by directory to destination
while read line; do
    mv $sourcedir$line/* $destdir$line/
    echo "All files from directory $sourcedir$line moved to $destdir$line"
done < temp.txt

rm temp.txt

1
你应该双引号变量引用,并使用 while IFS= read line 来确保嵌入空格和其他 shell 元字符的文件名被正确处理。 除了寻找子目录名称的低效和绕路方式(使用 find 代替),你不仅找到了 目录,而且找到了整个 子树 中的 所有 目录,这将在移动时失败,因为当你到达一个子子目录时,它已经被移动了。这可能不是 OP 案例中的问题(听起来似乎没有子子目录),但值得指出。 - mklement0
感谢您宝贵的建议。我是脚本的初学者,尝试用自己的方式回答问题。当您说双引号变量引用时,是指在“mv”命令中使用变量时要使用双引号吗?是的,我考虑到除了子目录之外,不会有其他子目录存在。 - data-bite
仅返回子目录:glob */(注意末尾斜杠)将返回所有子目录(默认情况下排除隐藏的子目录)。至于双引号:是的,如果您想引用一个变量而不被shell解释,您必须使用双引号:"$var"。没有双引号,$var会受到单词拆分和路径名扩展(globbing)的影响。尝试以下内容:var='spaced out'; set -- $var; echo "Count of params: $#"var='A *'; echo $var - mklement0

0
你可以使用以下脚本:
#!/bin/bash
dirX=X
dirY=Y
for subdir in `ls $dirX`; do
        mv $dirX$subdir/* $dirY$subdir
done

将 X、Y 替换为您的目录


1
请不要使用 for 命令来解析命令输出 [http://mywiki.wooledge.org/DontReadLinesWithFor],也不要解析 ls 命令的输出 [http://mywiki.wooledge.org/ParsingLs] (另外:ls $dirX 命令将匹配文件)并且在引用变量时要加上双引号。 - mklement0
除了ls会匹配文件,我觉得这个脚本没有任何问题。 - Waveter
1
bash 中交互式地运行以下命令:mkdir -p /tmp/from/{'a b','c d'} /tmp/to; touch /tmp/from/{'a b','c d'}/{'f 1','f 2'};然后,在您的脚本中设置 dirX=/tmp/fromdirY=/tmp/to。 现在运行您的脚本并观察发生了什么。 - mklement0
1
感谢您的指示,我看到了问题。 - Waveter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接