Frage wie man einen bestimmten Bereich von Dateien von einem Verzeichnis zu einem anderen verschiebt


In meinem Elternverzeichnis habe ich 10000 Dateien, ich möchte die Dateien in 4 Unterverzeichnisse subdir1, subdir2, subdir3, subdir4 gleich und wenn möglich gleichzeitig kopieren oder verschieben. Ist es möglich, einen bestimmten Bereich von Dateien aus dem übergeordneten Verzeichnis in die Unterverzeichnisse zu kopieren, d.h.

1-2500 Dateien zu Unterverzeichnis1
2500-5000 Dateien in Unterverzeichnis2
5000-7500 Dateien zu Unterverzeichnis3
7500-10000 Dateien zu Unterverzeichnis4

All dies sollte mit einer Batch-Datei erfolgen. Ist es möglich? Bitte helfen Sie mir, wenn jemand weiß.

Danke im Voraus.


3
2018-04-27 11:18


Ursprung


kein Problem, aber wie würde der Auftrag entschieden? alphabetisch? - Jacob Vlijm


Antworten:


Dieser funktioniert für eine beliebige Anzahl von Dateien und kann mit seltsamen Dateinamen umgehen (die Leerzeichen, Zeilenumbrüche, Backslashes oder andere Seltsamkeiten enthalten):

#!/usr/bin/env bash

## This will keep track of the number of files processed
num=0;
## This is used to choose the righht subdir
dir=1;
## The initial value of the target directory
target="subdir1"

for file in *; do 
    ## Skip unless this is a file
    if [ -f "$file" ]; then
        ## Create the target directory if it doesn't exist
        [ -d "$target" ] || mkdir  "$target"
        ## Move the current file
        mv "$file" "$target"
        ## Change the target if we're at a multiple of 2500
        if [[ $(( ++num % 2500 )) -eq 0 ]]; then
            target="subdir"$((++dir));
        fi
    fi
done

Sie können das Gleiche auch mit implementieren find:

#!/usr/bin/env bash

## This will keep track of the number of files processed
num=0;
## This is used to choose the right subdir
dir=1;
## The initial value of the target directory
target="subdir1"

## Run your find, with -print0 to print NUL-separated values. This
## is needed for file names that contain newlines
find . -type f -print0 |
    ## The IFS= makes this deal with spaces, the -d ''  sets the input delimiter
    ## to NUL so ti can work with -print0 and the -r makes it work with backslashes
    while IFS= read -d '' -r file; do
    ## Create the target directory if it doesn't exist
    [ -d "$target" ] || mkdir  "$target"
    ## Move the current file
    mv "$file" "$target"
    ## Change the target if we're at a multiple of 2500
    if [[ $(( ++num % 2500 )) -eq 0 ]]; then
        target="subdir"$((++dir));
    fi
    done             

Speichern Sie das Skript als ~/bin/batch_rename.shmach es ausführbar (chmod a+x ~/bin/batch_rename.sh) und dann führe es aus aus dem Verzeichnis, in dem sich die Dateien befinden.


ANMERKUNGEN

  • Das erste Beispiel findet nur Dateien im aktuellen Verzeichnis. Um es rekursiv zu machen, füge diese Zeile am Anfang hinzu:

    shopt -s globstar
    

    Dann ändere die for file in * zu for file in **/*.

  • Das zweite Beispiel findet alle Dateien in diesem und jedem Unterverzeichnis. Das kann oder darf nicht sein, was Sie wollen.


2
2018-04-27 15:28





Wenn die Bestellung kein Problem ist, das folgende Skript:

  • teilt die Dateien in (beliebige) Chunks auf
  • erstellt die Unterverzeichnisse pro Chunk (chunk_1, chunk_2 etc.)
  • verschiebt die entsprechenden Dateien in die Unterverzeichnisse

Beachten Sie, dass:

  • Wenn die Bestellung ist ein Problem, das Skript muss geringfügig angepasst werden, aber fügen Sie dann bitte die Bestellregeln in die Frage ein.
  • Das Skript berücksichtigt Dateien (-names) nicht mit Leerzeichen usw. Auch der Name des Unterverzeichnisses "body" darf Leerzeichen enthalten.

Das Skript

#!/usr/bin/env python3
import os
import shutil
import sys
#--- if desired, change the sub dir's name body below
namebody = "chunk_"
#---
dr = sys.argv[1]; size = int(sys.argv[2]); 
files = [f for f in os.listdir(dr) if os.path.isfile(dr+"/"+f)]

n = max(1, size)
chunks = [files[i:i + size] for i in range(0, len(files), size)]
for i, item in enumerate(chunks):
    subfolder = os.path.join(dr, namebody+str(i+1))
    if not os.path.exists(subfolder):
        os.makedirs(subfolder)
    for f in chunks[i]:
        shutil.move(dr+"/"+f, subfolder+"/"+f)

Wie benutzt man

  1. Kopieren Sie das Skript in eine leere Datei, speichern Sie es unter reorganize.py
  2. Falls gewünscht, können Sie den Namen des Unterverzeichnisses "body" (den Namensteil ohne Nummer) im Kopf des Skripts ändern, in:

    namebody = "chunk_"
    
  3. Führen Sie es mit dem Hauptverzeichnis und der Chunk-Größe als Argumente aus:

    python3 /path/to/reorganize.py <main_directory> <chunk_size>
    

1
2018-04-27 12:21



Bei Python ist es besser, nach Vergebung als nach Erlaubnis zu fragen, also fangen Sie den von os.makedirs angesprochenen OSError ab, wenn er fehlschlägt, anstatt vorher zu prüfen. Außerdem können Sie anstelle von range (len (chunks)) über Listen selbst iterieren - kiri
@ minerz029 Die range(len(chunks)) ist, die Indexnummer zu erhalten, um nummerierte Unterordner zu erstellen. - Jacob Vlijm
Benutzen for index, item in enumerate(chunks): um den Index und das Element in Blöcken zu erhalten - kiri
@minerz029 try/except  in dem os.makedirs(subfolder)  -Sektion würde es länger machen, die in dieser Umgebung nicht sehr sexy ist, denke ich :). - Jacob Vlijm


Beyogen auf mein altes Skript mit einigen Änderungen (nur die N-Wert und Variablennamen :) geändert:

Gruppieren Sie alle N Dateien in separaten Verzeichnissen

##grouping each N files in separate directories

echo 1 > dircount; 
find source -type f -name 'filterFileName' -print0 | sort -z --version-sort | \
  xargs -0n2500 bash -c 'read TARGET <dircount; \
   echo mkdir -p "subdir$TARGET"; \
   echo mv -t "subdir$TARGET" "$@"; \
  expr $TARGET + 1 >dircount' grouping
  1. -print0 druckt die durch den NUL getrennten Dateinamen\0) Zeichen. Es ist der sicherste Weg, Dateinamen als Ausgabe an andere Befehle zu übergeben.
  2. sort mit -z sucht nach Null-Eingrenzung und die --version-sort erlaubt das sichere Sortieren von Zahlen mit variabler Länge, so dass filename2.xyz vor fileName3.xyz steht
  3. xargs mit -n2500 Begrenzen Sie die Anzahl der Argumente für jeden Befehl (in diesem Fall 2500 Argumente). Das -0 ist für Null-getrennte Eingabe.

Hinweis: Vergessen Sie nicht, dass Sie das Skript im Testfall ausführen echo Befehl neben den verwandten Zeilen zu Ausführen, um das aktuelle Skript auszuführen.


Außerdem gibt es zwei weitere Skripte, die nicht so schnell sind wie oben:

groupFiles=0
TARGET=1
for file in `ls -v /path/to/source/filterFileName` ; do
    mkdir -p "subdir$TARGET" && mv "$file" "subdir$TARGET" 
    [[ ++groupFiles -eq 2500 ]] && groupFiles=0 &&  ((TARGET++))
done
  • ls -v Der Befehl sortiert die Dateien als natürliche Art von (Versions) -Nummern.
    Sie können das analysieren ls Befehl, wenn der Dateiname kein Leerzeichen, keine neue Zeile usw. hat
  • mkdir -p "subdir$TARGET" kettet das Verzeichnis basierend auf TARGET Variable.

  • mv "$file" "subdir$TARGET" verschiebt die Datei in das Verzeichnis welches TARGET spezifiziert.

  • Setzen Sie die groupFiles=0 auf Null, wenn 2500 Dateien verschoben wurden ([[ ++groupFiles -eq 2500 ]]) und erhöhen auf TARGET Wert.

Beachten Sie diese Änderung /path/to/source/ zu Ihrem tatsächlichen Quellverzeichnis.

Wenn Sie das nicht analysieren wollen ls Befehl fällige Dateinamen einschließlich Leerzeichen, Zeilenumbrüche und usw., hier ist eine weitere Option zu tun:

groupFiles=0
TARGET=1
find /path/to/source/ -type f -name 'filterFileName' -print0 | \
     sort -z --version-sort | while IFS= read -d '' -r file; do \
     mkdir -p "subdir$TARGET" && mv "$file" "subdir$TARGET" ;
     [[ ++groupFiles-eq 7 ]] && groupFiles=0 &&  ((TARGET++));
done

0
2018-04-27 18:16



Dein erstes sollte in Ordnung sein, aber noch nie machen for i in ls, das scheitert an allem, einschließlich einfacher Leerzeichen. Parsing ls ist nie eine gute Idee, wie Sie darauf hinweisen, aber es in einem tun for Loop fragt nach Ärger. Zumindest benutzen ls | while.... - terdon♦


Dies bash Das Skript verschiebt eine beliebige Anzahl von Dateien, die im Zielverzeichnis vorhanden sind, als Argument, das sie gleichmäßig über eine beliebige Anzahl von Dateien verteilt, die als Argument der angegebenen Zielunterverzeichnisse an sie übergeben werden subdir<N>Erstellen der Zielunterverzeichnisse, wenn sie nicht bereits vorhanden sind; Es sollte außerhalb des Zielverzeichnisses liegen, gegen das es ausgeführt werden soll, damit es während der Ausführung nicht verschoben wird.

Verwendung:

./script.sh <path_to_target_directory> <number_of_subdirectories>

* <path_to_target_directory> = Pfad zu dem Verzeichnis, das die zu unterteilenden Dateien enthält; <number_of_subdirectories> = Anzahl der Unterverzeichnisse, in die die Dateien unterteilt werden sollen

#!/bin/bash

for i in `seq 1 "${2}"`
do
    mkdir -p \'"${1}"/subdir"${i}"\'
done
j=0
find \'"${1}"\' -maxdepth 1 -type f | while read -r filepath
do
    N=$(( ${j} % ${2} + 1 ))
    mv \'"${filepath}"\' \'"${1}/subdir${N}"\'
    ((${j}++))
done

Ergebnisse für ./script.sh ~/testdir 4:

Vor:

~ / testdir
1── 1
10── 10
2── 2
3── 3
4── 4
5── 5
6── 6
7── 7
8── 8
9── 9

Nach:

~ / testdir
Subd── Unterverzeichnis1
│ ├── 1
│ ├── 2
│ └── 7
Subd── Unterverzeichnis2
│ ├── 10
│ ├── 6
│ └── 8
Subd── Unterverzeichnis3
│ ├── 3
│ └── 9
Subd── Unterverzeichnis4
    4── 4
    5── 5

0
2018-04-27 12:26



Warum übergibst du die Ausgabe von find als eine Schnur? Warum nicht find | while read foo; do ...; done? In jedem Fall bricht dies auch Dateinamen mit Leerzeichen oder Backslashes in ihren Namen. - terdon♦
@terdon Aktualisiert, danke für die Vorschläge - kos