Перемещение файлов после сравнения имен файлов и воссоздания исходных каталогов - PullRequest
0 голосов
/ 06 декабря 2018

Я изучаю сценарии оболочки и стараюсь быть как можно более совместимым с POSIX, сохраняя кодовую базу несколько читабельной.Цель состоит в том, чтобы прочитать список файлов из каталога A, найти их совпадения из каталога B и воссоздать часть родительского каталога B в каталоге C, куда следует переместить файлы из каталога A, а затем удалить сопоставленные / перемещенные файлы изкаталог B, и, если каталоги пустые из найденных файлов каталога B, удалите их.Все файлы в каталоге A всегда будут уникальными друг для друга, и всегда будет одно или несколько совпадений из каталога B и никогда не совпадений в каталоге C, но подкаталоги в каталоге C могут уже присутствовать для совпадения из каталога BВсе файлы, соответствующие в каталоге B, должны быть удалены после перемещения совпадений из каталога A в каталог C. Расширения изменяются по мере того, как файлы обрабатываются отдельно, но имена файлов в противном случае будут точно совпадать.Имена файлов могут содержать пробелы и точки.Имена файлов не всегда будут одинаковой длины.В выходных и архивных каталогах есть два уровня подкаталогов.

Вот что я получил до сих пор.Я застреваю при написании цикла for, чтобы выполнить грязную работу.Стараясь не заходить слишком далеко за пределы функции find, printf, awk, grep, for и if.

#!/bin/sh
execHome="intendedMachine"
baseDir="/home/library/projects"
folderNew="output"
folderOld="working"
folderArchive="archive"
workingTypes=("jpg", "svg", "bmp", "tiff", "psd")

$folderNew="$baseDir/$folderNew"
$folderOld="$baseDir/$folderOld"
folderArchive="$baseDir/$folderArchive"

if [ "$(uname -n)" = "$execHome" ]
then

  count=$(find $folderNew -type f |grep -v "DS_Store" |awk -F "/" '{print $NF}'|wc -l)

  printf "\nFound/processing %s files in the %s folder\n\n" "$count" "$folderNew"

  find $folderNew -type f |grep -v "DS_Store" |awk -F "/" '{print $NF}'

else
  printf "Executed from %s; Run from %s for proper execution.\n" "$(uname -n)" "$execHome"
fi

Пример:

Каталог A

/home/library/projects/output/projectOne 1.a.png
/home/library/projects/output/projectOne 1.b.png
/home/library/projects/output/projectOne 1.c.png
/home/library/projects/output/projectThree 3.m.png
/home/library/projects/output/projectThree 3.o.png
/home/library/projects/output/projectFour 4.t.png
/home/library/projects/output/projectFour 4.u.png

Каталог B

/home/library/projects/working/House/2018 01/projectOne 1.a.jpg
/home/library/projects/working/House/2018 01/projectOne 1.a.svg
/home/library/projects/working/House/2018 01/projectOne 1.b.jpg
/home/library/projects/working/House/2018 01/projectOne 1.b.svg
/home/library/projects/working/House/2018 01/projectOne 1.c.jpg
/home/library/projects/working/House/2018 02/projectTwo 2.g.jpg
/home/library/projects/working/House/2018 02/projectTwo 2.g.svg
/home/library/projects/working/House/2018 02/projectTwo 2.h.jpg
/home/library/projects/working/House/2018 02/projectTwo 2.h.svg
/home/library/projects/working/House/2018 02/projectTwo 2.i.jpg
/home/library/projects/working/Car/2018 03/projectThree 3.m.jpg
/home/library/projects/working/Car/2018 03/projectThree 3.n.jpg
/home/library/projects/working/Car/2018 03/projectThree 3.o.jpg
/home/library/projects/working/Car/2018 03/projectThree 3.o.svg
/home/library/projects/working/Car/2018 04/projectFour 4.s.jpg
/home/library/projects/working/Car/2018 04/projectFour 4.t.jpg
/home/library/projects/working/Car/2018 04/projectFour 4.u.jpg

Каталог C

/home/library/projects/archive/House/2018 01/projectOne 1.d.png
/home/library/projects/archive/House/2018 01/projectOne 1.e.png
/home/library/projects/archive/House/2018 01/projectOne 1.f.png
/home/library/projects/archive/Car/2018 03/projectThree 3.p.png
/home/library/projects/archive/Car/2018 03/projectThree 3.q.png
/home/library/projects/archive/Car/2018 03/projectThree 3.r.png

Желаемый результат:

Каталог A Файлы перемещены в Каталог C

/home/library/projects/output/

В каталоге B должны быть удалены файлы каталога A и удалены пустые папки

/home/library/projects/working/House/2018 02/projectTwo 2.g.jpg
/home/library/projects/working/House/2018 02/projectTwo 2.g.svg
/home/library/projects/working/House/2018 02/projectTwo 2.h.jpg
/home/library/projects/working/House/2018 02/projectTwo 2.h.svg
/home/library/projects/working/House/2018 02/projectTwo 2.i.jpg
/home/library/projects/working/Car/2018 03/projectThree 3.n.jpg
/home/library/projects/working/Car/2018 04/projectFour 4.s.jpg

В каталоге C должны содержаться как старые архивы, так и новые выходные файлы в виде архивов

/home/library/projects/archive/House/2018 01/projectOne 1.a.png
/home/library/projects/archive/House/2018 01/projectOne 1.b.png
/home/library/projects/archive/House/2018 01/projectOne 1.c.png
/home/library/projects/archive/House/2018 01/projectOne 1.d.png
/home/library/projects/archive/House/2018 01/projectOne 1.e.png
/home/library/projects/archive/House/2018 01/projectOne 1.f.png
/home/library/projects/archive/Car/2018 03/projectThree 3.m.png
/home/library/projects/archive/Car/2018 03/projectThree 3.o.png
/home/library/projects/archive/Car/2018 03/projectThree 3.p.png
/home/library/projects/archive/Car/2018 03/projectThree 3.q.png
/home/library/projects/archive/Car/2018 03/projectThree 3.r.png
/home/library/projects/archive/Car/2018 04/projectFour 4.t.png
/home/library/projects/archive/Car/2018 04/projectFour 4.u.png

В любом случае, запустил код с машины bash 4.4.19, чтобы увидеть, как он работает, но он не сработал так, как я ожидал.Вот результат:

Found/processing 4 files in the /home/library/projects/output folder

./auto-archive.sh: line 34: hash["$proj"]: bad array subscript
parent of /home/library/projects/output/.temp/projectThree 3.m.png not found
parent of /home/library/projects/output/projectOne 1.a.png not found
parent of /home/library/projects/output/.temp/projectThree 3.0.png not found
parent of /home/library/projects/output/projectFour 4.t.png not found

Мои извинения.Я также не упомянул ранее, что Каталог B не должен сканироваться рекурсивно, что в сценарии использования дает другие временные файлы, которые записываются, но, возможно, еще не готовы к перемещению.Кроме того, для целей тестирования только четыре файла, перечисленных выше, фактически находились в каталоге A;не все файлы перечислены изначально.Кроме того, после воссоздания предложенной тестовой структуры ваш код, кажется, работает безупречно;не совпадает с результатами моей фактической файловой структуры.Я боюсь, что, возможно, упустил какой-то важный элемент в описании моего фактического соглашения о структуре файлов / именованииТеперь рассмотрим различия в дескрипторах.Извините, что отнял время, но, безусловно, впечатлен вашей точностью.Такое ощущение, что мы приближаемся, но определенно нужно запустить более раннюю версию bash.

1 Ответ

0 голосов
/ 06 декабря 2018

Задача будет разделена на три этапа:

  1. Чтобы создать карту, которая связывает каждое имя файла (имя проекта) с именем его родительского каталога в C. Это выполняется в качестве подготовкиприступим к анализу путей в B. Мы будем использовать ассоциативный массив, и версия bash должна быть 4.2 или новее .

  2. Для циклического перемещения файлов в Aсоставьте имя пути для хранения в C, используя карту, созданную на 1-м шаге, и удалите файлы в B.

  3. В качестве этапа очистки мы удаляем пустые каталоги вB, если есть.

Тогда как насчет:

#!/bin/bash

execHome="intendedMachine"
baseDir="/home/library/projects"
folderNew="output"
folderOld="working"
folderArchive="archive"
workingTypes=("jpg" "svg" "bmp" "tiff" "psd")
declare -A hash

folderNew="$baseDir/$folderNew"
folderOld="$baseDir/$folderOld"
folderArchive="$baseDir/$folderArchive"

if [ "$(uname -n)" != "$execHome" ]; then
    printf "Executed from %s; Run from %s for proper execution.\n" "$(uname -n)" "$execHome"
    exit
fi

count=$(find "$folderNew" -type f |grep -v "DS_Store" |awk -F "/" '{print $NF}'|wc -l)
printf "\nFound/processing %s files in the %s folder\n\n" "$count" "$folderNew"

# determine parent directory name for each project name and create a map for them
while IFS=  read -r -d $'\0' f; do 
    proj="${f##*/}"         # remove dirname
    proj="${proj%.*}"               # remove extention
    parent="${f##*$baseDir/}"       # remove pathname until $baseDir
    parent="${parent#*/}"   # strip pathname one-level deeper
    parent="${parent%/*}"   # remove filename
    # now we're mapping "projectOne 1.a" => "House/2018 01" e.g.
#   echo "$proj" "=>" "$parent"     # just for debugging
    hash["$proj"]="$parent"
done < <(find "$folderOld" -type f -print0) # directory B

# iterate over files in A; move to archive directory C and remove files in B
while IFS=  read -r -d $'\0' f; do
    proj="${f##*/}"
    proj="${proj%.*}"
    parent="${hash[$proj]}"
    if [[ "$parent" = "" ]]; then
    echo "parent of $f not found"   # may not occur but just in case ..
    else
    # move from A to C
    destdir="$folderArchive/$parent"
    mkdir -p -- "$destdir"
    mv -- "$f" "$destdir"

    # remove relevant file(s) in B
    for ext in "${workingTypes[@]}"; do
        oldfile="$folderOld/$parent/$proj.${ext}"
        [ -f "$oldfile" ] && rm -f -- "$oldfile"
    done
    fi
done < <(find "$folderNew" -type f -print0) # directory A

# clean-up: remove empty dirs in B
find "$folderOld" -type d -empty -print0 | xargs -r -0 rmdir --

Пояснения:

  • Вы недля разделения элементов в массиве необходимо использовать запятые.
  • Не следует ставить $ перед именем переменной слева.
  • Синтаксис while IFS= ... done < <(find ...) является идиомойдля циклического вывода вывода find.
  • Синтаксис типа ${parameter#word} - это parameter expansion для извлечения подстроки из пути.
  • Ассоциативный массив hash сопоставляет каждое имя проекта, например, "projectOne 1.a", с именем его родительского каталога, например "House / 2018 01".
  • -- s внекоторые команды должны подготовить имена файлов, которые могут начинаться с -.(эта защита может выглядеть патологически ...)

Если ваш bash старше 4.2, дайте мне знать.Затем нам нужно найти альтернативу.

EDIT
В качестве альтернативы приведена версия, совместимая с POSIX:
(Очевидно, что скрипт не работает, если имена файлов содержат символ новой строки илиescape-символ \x1b.)

#!/bin/sh

execHome="intendedMachine"
baseDir="/home/library/projects"
folderNew="output"
folderOld="working"
folderArchive="archive"
workingTypes="jpg
svg
bmp
tiff
psd"

folderNew="$baseDir/$folderNew"
folderOld="$baseDir/$folderOld"
folderArchive="$baseDir/$folderArchive"
nl="
"                   # set to newline character
esc=$(/bin/echo -ne "\033")      # set to escape character
#esc=":"            # if \033 does not work well, try another character

# substitute of reading a hash
# it relies on the context that IFS is set to $nl
read_lut() {
    local i
    local key
    local val
    local ret=""
    for i in $lut; do
        key="${i%${esc}*}"
        val="${i#*${esc}}"
    if [ "$key" = "$1" ]; then
        # loop until the end and use the last value
        ret="$val"
    fi
    done
    echo "$ret"
}

# substitute of writing to a hash
write_lut() {
    lut=$(printf "%s\n%s%c%s" "$lut" "$1" "$esc" "$2")
}

if [ "$(uname -n)" != "$execHome" ]; then
    printf "Executed from %s; Run from %s for proper execution.\n" "$(uname -n)" "$execHome"
    exit
fi

count=$(find "$folderNew" -type f |grep -v "DS_Store" |awk -F "/" '{print $NF}'|wc -l)
printf "\nFound/processing %s files in the %s folder\n\n" "$count" "$folderNew"

# determine parent directory name for each project name and create a map for them
ifs_bak="$IFS"
IFS="$nl"
for f in $(find "$folderOld" -type f); do
    proj="${f##*/}"         # remove dirname
    proj="${proj%.*}"               # remove extention
    parent="${f##*$baseDir/}"       # remove pathname until $baseDir
    parent="${parent#*/}"   # strip pathname one-level deeper
    parent="${parent%/*}"   # remove filename
    # now we're mapping "projectOne 1.a" => "House/2018 01" e.g.
#   echo "$proj" "=>" "$parent"     # just for debugging
    write_lut "$proj" "$parent"
done

# iterate over files in A; move to archive directory C and remove files in B
for f in $(find "$folderNew" -type f); do
    proj="${f##*/}"
    proj="${proj%.*}"
    parent=$(read_lut "$proj")
    if [ "$parent" = "" ]; then
        echo "parent of $f not found"   # may not occur but just in case ..
    else
        # move from A to C
        destdir="$folderArchive/$parent"
        mkdir -p -- "$destdir"
        mv -- "$f" "$destdir"

        # remove relevant file(s) in B
        for ext in $workingTypes; do
            oldfile="$folderOld/$parent/$proj.${ext}"
            [ -f "$oldfile" ] && rm -f -- "$oldfile"
        done
    fi
done

# clean-up: remove empty dirs in B
find "$folderOld" -type d -empty -print0 | xargs -r -0 rmdir --

# restore IFS
IFS="$ifs_bak"
...