Перемещение большого количества файлов в каталоги на основе имен файлов в Linux

Вы должны использовать "$0"и dirname "$()".

Команды не эквивалентны, поскольку проверка оболочки влияет только на последний компонент в пути, а readlink -fвлияет на все уровни пути.

mkdir physdir
touch physdir/file
ln -s physdir symlink
test -L symlink/file ; echo $?
    1
test -L symlink ; echo $?
    0
dirname symlink/file
    symlink
dirname "$(readlink -f symlink/file)"
    /crypto/home/hl/tmp/stackexchange/readlink/physdir

Таким образом, для относительных путей вывод сильно отличается, но даже для абсолютных путей каталоги символических ссылок внутри различаются.

1
15.09.2020, 02:01
3 ответа

Не проверено

Я бы сделал что-то вроде:

#!/bin/bash
bottom=0
while [[ $bottom -lt 150000 ]] ; do
    myfirst=$((bottom + 1))
    mylast=$((bottom + 100000))
    bottom=$((bottom + 100000))

    dir="${myfirst}_$mylast"
    [[ -d "$dir" ]] || mkdir "$dir"
    seq $myfirst $mylast | \
        while read p ; do
            q="${p}_file.txt"
            [[ -f "$q" ]] && echo "$q"
        done | \
            xargs --no-run-if-empty  echo mv -t "$dir"

done

Удалите echoиз echo mv, если хотите сделать это по-настоящему.

2
18.03.2021, 23:04

script.sh

#!/bin/bash

step=100000
file_dir=$1

# Counting of files in the directory
shopt -s nullglob
file_list=("${file_dir}"/*)
file_num=${#file_list[@]}

# Every file's common part
suffix='_file.txt'

for((from = 1, to = step; from <= file_num; from += step, to += step)); do
    new_dir="${from}_${to}"
    mkdir "${file_dir}/${new_dir}"

    if ((to > file_num)); then
        to="$file_num"
    fi
    
    # Generating filenames by `seq` command and passing them to `xargs`
    seq -f "${file_dir}/%.f${suffix}" "$from" "$to" | xargs mv -t "${file_dir}/${new_dir}"
done

Использование:./script.sh files

Тестирование

Я сгенерировал файлы этой командой:

printf '%s\0' files/{1..1455728}_file.txt | xargs -0 touch

затем выполните:

$ time./script.sh files

# Time is:
real    10m43,618s
user    0m9,953s
sys 0m19,671s

Довольно медленно.

Результат

$ ls -1v files
1_100000
100001_200000
200001_300000
300001_400000
400001_500000
500001_600000
600001_700000
700001_800000
800001_900000
900001_1000000
1000001_1100000
1100001_1200000
1200001_1300000
1300001_1400000
1400001_1500000
0
18.03.2021, 23:04

В оболочке возможна арифметика, но она всегда неудобна, поэтому я рекомендую вам поискать другой язык сценариев, который сделает большую часть работы здесь. В следующем примере используется awk, но с таким же успехом можно использовать и perl. Я хотел бы иметь возможность сказать, что вы также можете легко использовать pythonв приведенном ниже примере, но аспекты синтаксиса pythonделают не очевидным, как встроить скрипт python в строку -в конвейер. так. (Это можно сделать, но это раздражающе сложно. )Обратите внимание, что я не использую awkдля выполнения ходов,просто для выполнения расчетов, необходимых для создания необходимого каталога назначения. Если вы используете perlили python, они также могут выполнять операции с файловой системой.

Некоторые предположения:

  • Вы хотите переместить файл с его полным исходным именем. Не намного сложнее изменить сценарий, чтобы удалить числовой префикс исходного (, хотя тогда лучше, чтобы файлы не заканчивались на_file.txt).

  • В именах файлов есть только одна _и нет пробелов. Если это не так, что-то вроде следующего все еще может работать, но вам нужно быть более осторожным в сценарии awk и следующем цикле оболочки.

Таким образом, с учетом этих условий должно работать следующее.

ls | 
awk -F_ '
{
    n = $1 - 1               # working zero based is easier here
    base = n - (n % 100000)  # round down to the nearest multiple of 100,000
    printf "%d_%d %s_%s\n", base + 1, base + 100000, $1, $2
}' |
while read destdir orig
do
    mkdir -p $destdir 
    mv $orig $destdir
done

Итак, что здесь происходит?

ls |...

Здесь просто перечислены имена файлов, и, поскольку вывод идет в канал, а не в терминал, они перечислены по одному в строке. Файлы будут отсортированы по порядку lsпо умолчанию, но остальная часть скрипта не заботится об этом и будет нормально работать со случайным списком имен файлов.

... | awk -F_ '
{
    n = $1 - 1               # working zero based is easier here
    base = n - (n % 100000)  # round down to the nearest multiple of 100,000
    printf "%d_%d %s_%s\n", base + 1, base + 100000, $1, $2
} |...'

Это несложно, но если вы никогда не играли с awkраньше, это будет сложно понять. Во-первых, цель здесь состоит в том, чтобы прочитать имена файлов по одному из ls, а затем для каждого имени файла создать строку вывода с двумя полями :первое поле с соответствующим каталогом назначения для исходного имени файла, а второе поле, передающее исходное имя файла, чтобы его могла использовать следующая часть конвейера. Итак, более подробно

  • Флаг -F_для awkуказывает ему разбивать каждую строку ввода на поля по символу _. Предполагая, что _встречается в этих именах файлов только один раз, awk назначит $1числовой части имени и $2всему тексту после _. Затем применяется раскосный блок с настройками $1и $2, как только что описано.

  • Вычисление baseопределяет, к какому блоку из 100000 файлов принадлежит этот файл. Сначала вычислите n, вычитая 1из начального числа имени файла. Этот ноль -лежит в основе числа, что облегчает работу с модульной арифметикой, используемой в следующей строке. Затем округлите nв меньшую сторону до ближайшего числа, кратного 100 000. Если nуже кратно 100 000, его не трогают. (Если вы не знакомы с оператором '%', то он N % Mвычисляет остаток при делении Nна M. Итак, 5 % 3 == 2, 6 % 3 == 0и так далее.)

  • Наконец, printfсобирает выходную линию, необходимую для следующего этапа конвейера. Он создает строку с двумя полями, разделенными пробелом. Первый — это имя целевого каталога, сгенерированное с помощью baseдля получения верхней и нижней частей имени каталога; именно здесь мы возвращаемся к схеме подсчета на основе 1 -для вывода. Второе поле представляет собой реконструированное исходное имя входного файла.

... | while read destdir orig
do
    mkdir -p $destdir && mv $orig $destdir
done

Это последний этап конвейера, фактически выполняющий все действия. Он считывает каждую строку, созданную сценарием awk, как два поля, а затем

  • он гарантирует существование каталога, используя mkdir -p(, который ничего не делает, если каталог уже существует ),
  • и в случае успеха перемещает исходный файл в новый каталог.

Часто хорошей идеей является использование шаблона mkdir... && mv...в сценариях оболочки, потому что, если mkdirне удается по какой-либо причине, попытка переименования не предпринимается.

Этот шаблон из нескольких стадий конвейера, каждая из которых постепенно преобразует данные каким-то простым, но полезным способом, является очень эффективным способом написания многих видов сценариев оболочки. Он играет на сильных сторонах оболочки в управлении процессами и конвейерами,в то же время позволяя вам выполнять более сложные вычисления, с которыми оболочка не справляется, в более подходящие языки.

0
18.03.2021, 23:04

Теги

Похожие вопросы