【问题标题】:How to copy the contents of a folder to multiple folders based on number of files?如何根据文件的数量将文件夹的内容复制到多个文件夹?
【发布时间】:2019-10-06 02:09:00
【问题描述】:

我想根据文件的数量(这里:50)将文件从文件夹(名称:1)复制到多个文件夹。

下面给出的代码有效。我根据文件数将文件夹中的所有文件传输到子文件夹,然后将目录中的所有文件复制回初始文件夹。 但是,我需要更清洁、更高效的东西。为下面的混乱道歉,我是nube。

bf=1 #breakfolder
cd 1 #the folder from where I wanna copy stuff, contains 179 files

flies_exist=$(ls -1q * | wc -l) #assign the number of files in folder 1

#move 50 files from 1 to various subfolders

while [ $flies_exist -gt 50 ]
do

mkdir ../CompiledPdfOutput/temp/1-$bf
set --
for f in .* *; do
  [ "$#" -lt 50 ] || break
  [ -f "$f" ] || continue
  [ -L "$f" ] && continue
  set -- "$@" "$f"
done

mv -- "$@" ../CompiledPdfOutput/temp/1-$bf/
flies_exist=$(ls -1q * | wc -l)
bf=$(($bf + 1))
done

#mover the rest of the files into one final subdir

mkdir ../CompiledPdfOutput/temp/1-$bf
set --
for f in .* *; do
  [ "$#" -lt 50 ] || break
  [ -f "$f" ] || continue
  [ -L "$f" ] && continue
  set -- "$@" "$f"
done
mv -- "$@" ../CompiledPdfOutput/temp/1-$bf/
#get out of 1
cd ..

# copy back the contents from subdir to 1
find CompiledPdfOutput/temp/ -exec cp {} 1 \;

需要的目录结构是:

        parent
  ________|________
  |               |
  1       CompiledPdfOutput
  |               |
(179)           temp
                  |
             ---------------
             |    |    |    |
            1-1  1-2  1-3  1-4
            (50) (50) (50) (29)

“()”里面的数字表示文件的个数。

顺便说一句,我的代码的最后一步给出了这个警告,如果有人能解释正在发生的事情和解决方案,我会很高兴。

cp: -r not specified; omitting directory 'CompiledPdfOutput/temp/'
cp: -r not specified; omitting directory 'CompiledPdfOutput/temp/1-4'
cp: -r not specified; omitting directory 'CompiledPdfOutput/temp/1-3'
cp: -r not specified; omitting directory 'CompiledPdfOutput/temp/1-1'
cp: -r not specified; omitting directory 'CompiledPdfOutput/temp/1-2'

我也不想复制目录,只复制文件,所以给 -r 会很糟糕。

【问题讨论】:

  • 尝试更改您的find cmd 以包含-type f。否则,如果这给出了您需要的结果,请将其标记为“已关闭”。或者至少把改进放在次要位置。最好将时间花在可以使您的流程或其他任务更轻松、更高效的其他代码上。一个合理(和好的)代码和一个很好的问题,考虑到这种性质的类似的写得很差的问题在这里出现的频率。继续发帖,祝你好运!

标签: linux bash


【解决方案1】:

假设您需要更紧凑/更高效的东西,您可以利用现有工具(find、xargs)来创建管道,从而无需使用 bash 对每个步骤进行编程。

以下会将文件移动到拆分文件夹中。它将找到文件,将它们分组,每个文件夹 50 个,使用 awk 生成输出文件夹,然后移动文件。解决方案不如原来的优雅:-(

find 1 -type f |
    xargs -L50 echo |
    awk '{ print "CompliedOutput/temp/1-" NR, $0 }' |
    xargs -L1 echo mv -t

附带说明,当前脚本将文件从“1”文件夹移动到编号文件夹,然后将文件复制回原始文件夹。为什么不直接将文件复制到编号的文件夹中。如果需要,您可以使用“cp -p”来保留时间戳。

支持带有新行(和空格)的文件名

澄清问题表明解决方案应使用嵌入新行(和空格)的文件名。这需要稍作改动以使用 NUL 字符作为分隔符。

  # Count number of output folders
DIR_COUNT=$(find 1 -type f -print0 | xargs -0 -I{} echo X | wc -l)
  # Remove previous tree, and create folder
OUT=CompiledOutput/temp
rm -rf $OUT
eval mkdir -p $OUT/1-{1..$DIR_COUNT}

# Process file, use NUL as separator
find 1 -type f -print0 | 
   awk -vRS="\0"  -v"OUT=$OUT" 'NR%50 == 1 { printf "%s/1-%d%s",OUT,1+int(NR/50),RS } { printf "%s", ($0 RS) }' |
   xargs -0 -L51 -t mv -t

对文件中的空格和新行进行了有限的测试。在我的机器上看起来不错。

【讨论】:

  • 如果您需要处理带有空格/换行符的文件,我相信您需要比 awk(python 或 Perl)“更强大”的东西来进行格式化。或者,您可以在处理过程中使用转义来隐藏空格。
  • 这本来是我的第一选择,但这是我必须处理的一个限制。代码不起作用,它所做的只是在 OUT 中创建 179 个文件夹
  • 您能否补充问题:(1)运行时环境:Window/Linux 版本(2)包含换行符/特殊字符的少数文件的名称。也许有可能复制您遇到的问题。
【解决方案2】:

我发现发布的脚本存在一些问题:

  • 每个文件夹最多复制50个文件的逻辑过于复杂,整个循环的代码重复容易出错。

  • 它使用$@ 位置参数数组用于内部存储目的。这个变量不是为此而设计的,最好使用新的专用数组。

  • 与其将文件移动到子目录然后再​​将它们复制回来,不如在第一步中复制它们而无需移动会更简单。

  • 不推荐解析ls的输出。

考虑这个替代的,更简单的逻辑:

  • 初始化一个空数组to_copy,以保留应复制的文件
  • 初始化文件夹计数器,用于计算目标文件夹
  • 循环遍历源文件
    • 像以前一样应用过滤器(如果不是文件则跳过)
    • 将文件添加到to_copy
    • 如果to_copy 包含目标文件数,则:
    • 创建目标文件夹
    • 复制to_copy中包含的文件
    • to_copy的内容清空
    • 递增folder_counter
  • 如果to_copy 不为空
    • 创建目标文件夹
    • 复制to_copy中包含的文件

类似这样的:

#!/usr/bin/env bash

set -euo pipefail

distribute_to_folders() {
    local src=$1
    local target=$2
    local max_files=$3

    local to_copy=()
    local folder_counter=1

    for file in "$src"/* "$src/.*"; do
        [ -f "$file" ] || continue

        to_copy+=("$file")
        if (( ${#to_copy[@]} == max_files )); then
            mkdir -p "$target/$folder_counter"
            cp -v "${to_copy[@]}" "$target/$folder_counter/"
            to_copy=()
            ((++folder_counter))
        fi
    done

    if (( ${#to_copy[@]} > 0 )); then
        mkdir -p "$target/$folder_counter"
        cp -v "${to_copy[@]}" "$target/$folder_counter/"
    fi
}

distribute_to_folders "$@"

要将path/to/1 中的文件分发到path/to/compiled-output 下最多50 个文件的目录中,您可以调用此脚本:

./distribute.sh path/to/1 path/to/compiled-output 50

顺便说一句,我的代码的最后一步给出了这个警告,如果有人能解释正在发生的事情和解决方案,我会很高兴。

当然。命令find CompiledPdfOutput/temp/ -exec cp {} 1 \; 查找文件和目录,并尝试复制它们。当cp 遇到目录并且未指定-r 参数时,它会发出您看到的警告。您可以使用-type f 为文件添加过滤器。如果文件不多,那么一个简单的 shell glob 就可以完成这项工作:

cp -v CompiledPdfOutput/temp/*/* 1

【讨论】:

  • 这行得通,谢谢。但是,正如我在问题中提到的,我想复制内容而不是移动它,有没有办法一次复制整个内容而不是将内容复制到子文件夹并将它们再次复制回主文件夹?
  • @Arsalan 哎呀!不知道为什么写mv,应该是cp,现在更正一下。事实上,如果你想复制文件而不是移动它们,你可以将上面所有的mv替换为cp
  • @Arsalan 我更新了我的答案以避免最后不必要的mv 步骤。你还需要什么吗?
【解决方案3】:

这会将文件复制到多个固定大小的文件夹。根据您的要求更改源、目标和文件夹大小。这也适用于带有特殊字符的文件名(例如'file 131!@#$%^&*()_+-=;?')。

source=1
target=CompiledPDFOutput/temp 
folderSize=50

find $source -type f -printf "\"%p\"\0" \
| xargs -0 -L$folderSize \
| awk '{system("mkdir -p '$target'/1-" NR); printf "'$target'/1-" NR " %s\n", $0}' \
| xargs -L1 cp -t

【讨论】:

  • 首先,我需要从目录内部工作,所以使用父级将是一个很大的禁忌。其次,它不适用于特殊字符。第三,将内容粘贴到 temp 的子文件夹中。第四,@dash-o 上的编辑不起作用,这是错误 <. line syntax error near unexpected token>|'><./test.sh: line 10: | xargs -L50 回声'>
  • @Arsalan 我已经更新了我的解决方案。这将使用特殊字符并将文件复制到 temp 的子文件夹中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-02-06
  • 1970-01-01
  • 2020-05-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-28
相关资源
最近更新 更多