1

I have multiple layered sub-directories, and I am trying to relocate all directories which contain files of a pattern into a new parent directory. I want to maintain the contents of the directories I wish to move, whether or not they have files in addition to those matching a pattern.

For example:

homedir/subdir/{file.txt, file.png, file.rtf}
homedir/subdir/{file.txt, file.png}
homedir/subdir/{file.txt, file.jpg}
homedir/subdir/subdir/{file.png, file.png, file.mp3}

I want each DIRECTORY containing "*.png" (along with any additional non-png contents that may be within the directory) to be moved to /dirPNG

So, the result would be:

homedir/subdir/{file.txt, file.jpg}
homedir/dirPNG/subdir/{file.txt, file.png, file.rtf}
homedir/dirPNG/subdir/{file.txt, file.png}
homedir/dirPNG/subdir/subdir/{file.png, file.png, file.mp3}
thanasisp
  • 7,802
  • 2
  • 26
  • 39
user528798
  • 11
  • 1
  • If `homedir/dirPNG` contained a match to `*.png` should it also move `subdir` and `subdir/*...` too? – roaima Jun 07 '22 at 17:16
  • 1
    Why don't you name them `subdir1`, `subdir2`, `subdir3` into your example? You can't have different subdirs with same paths. – thanasisp Jun 07 '22 at 17:41
  • @thansisp That's just placeholder name of directories. They have different names in reality. – user528798 Jun 07 '22 at 18:06
  • @roaima I'm not sure I understand. /dirPNG shouldn't have any matches because it's a folder I'll create to move the results into prior to running the command; so It'll be empty prior to execution of mv. I think. Sorry if I'm not clear. Trying to learn this all as I go. – user528798 Jun 07 '22 at 18:10
  • 1
    You must use different names into your post. Please have a look here: [Minimal, Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example) – thanasisp Jun 07 '22 at 18:10

2 Answers2

1

You can use a nested find. This version requires GNU find or similar that supports -maxdepth and -quit, but there are workarounds available for POSIX compatibility.

find homedir -depth -type d \
    -exec sh -c '[ -n "$(find "$@" -maxdepth 1 -type f -name "*.png" -print -quit)" ]' _ {} \; \
    -exec sh -c 'echo "Move $@"' _ {} \;

Replace or supplement echo "Move $@" with mv "$@" /dirPNG when you are absolutely sure the code is doing what you want.

It works by traversing directories depth first, searching for any files matching *.png in each directory. If there's a match the directory is moved.

As a result, if you have homedir/subdir containing subsubdir/a.png and also b.png, you'll get subsubdir moved before subdir, so they become peers in the target directory rather than hierarchical.

If this does not achieve your required result you can try removing -depth, but you will receive a large number of find: ‘subdir’: No such file or directory type errors during the traversal where find tries to descend into a directory that's already been moved. This isn't fatal but it's inelegant.

Regardless, you will get errors if you try to move a directory into the destination if there's a directory with the same name already present. You have not specified what should happen in this instance so an error and a refusal to move will have to suffice.

roaima
  • 107,089
  • 14
  • 139
  • 261
0

Here is the script that will do what you need, but be warned that what you are doing is dangerous if applied to your HOME directory.

The below script creates a batch file to perform the actions.

That allows you to review the intended actions before you actually perform the task, so that you may delete any lines that could spell disaster for you.

There are other comments in the script which I suggest you consider for future expanded functionality.

#!/bin/sh

BASE=`basename "$0" ".sh" `
TMP="/tmp/tmp.$$.${BASE}"       ; rm -f ${TMP}

START=`pwd`
BATCH="${START}/${BASE}.batch"

MODE=0
INSENS=0
ONE_PARTITION=""
while [ $# -gt 0 ]
do
    case "${1}" in
        "--suffix" ) MODE=1 ; STRNG="${2}" ; pattern_ident="RELOC_${STRNG}" ; shift ; shift ;;
        "--prefix" ) MODE=2 ; STRNG="${2}" ; pattern_ident="RELOC_${STRNG}" ; shift ; shift ;;
        "--single" ) ONE_PARTITION="-xdev" ; shift ;;
        "--insensitive" ) INSENS=1 ; shift ;;
        * ) echo "\n\t ERROR:  Invalid option used on command line.  Options allowed: [ --suffix | --prefix ] \n Bye!\n" ; exit 1 ; ;;
    esac
done

if [ ${MODE} -eq 0 -o -z "${STRNG}" ] ; then  echo "\n\t ERROR:  Must specify one of --suffix or --prefix values on the command line.\n Bye!\n" ; exit 1 ; fi

SEARCH_ROOT="${HOME}"
RELOCN_DIR="${HOME}/${pattern_ident}"

if [ ! -d "${RELOCN_DIR}" ]
then
    mkdir "${RELOCN_DIR}"
    if [ $? -ne 0 ] ; then  echo "\n\t ERROR:  Unable to create target directory for directory relocation actions.\n Bye!\n" ; exit 1 ; fi

fi 2>&1 | awk '{ printf("\t %s\n", $0 ) ; }'

### Ignore start directory itself
### Handling directories with spaces/characters in their name
rm -f "${TMP}.search"*

### Segregate dot dirs from others
find "${SEARCH_ROOT}" -mindepth 1 -maxdepth 1 -type d -print | sort >"${TMP}.search.raw"
    awk -F \/ '{ if( index( $NF, "." ) == 1 ) { print $0 } ; }' <"${TMP}.search.raw" >"${TMP}.search"
    awk -F \/ '{ if( index( $NF, "." ) == 0 ) { print $0 } ; }' <"${TMP}.search.raw" >>"${TMP}.search"

##########
#more "${TMP}.search"
#exit 0
##########


while read SEARCH_dir
do
    if [ -z "${SEARCH_dir}" ] ; then  break ; fi

    echo "\t Scanning: ${SEARCH_dir} ..." >&2

    ### insert function to dynamically remap ${PAT} to expanded set for case insensitive
    if [ ${INSENS} -eq 1 ]
    then
        sPAT="[Pp][Nn][Gg]"
    else
        sPAT="${STRNG}"
    fi

##########
#echo "sPAT = ${sPAT}" >&2
#exit 0
##########

    case ${MODE} in
        1)  
#########
#( eval find \"${SEARCH_dir}\" ${ONE_PARTITION} -type f -name \'\*\.${sPAT}\' -print | awk -F'/[^/]*$' '{print $1}' | more >&2 ) <&2
#exit 0
#########

            eval find \"${SEARCH_dir}\" ${ONE_PARTITION} -type f -name \'\*\.${sPAT}\' -print |
                awk -F'/[^/]*$' '{print $1}' | sort | uniq
            ;;
        2)  
            eval find \"${SEARCH_dir}\" ${ONE_PARTITION} -type f -name \'${sPAT}\*\' -print |
                awk -F'/[^/]*$' '{print $1}' | sort | uniq
            ;;
    esac
done <"${TMP}.search" >"${TMP}.dirsToMove"
if [ ! -s "${TMP}.dirsToMove" ] ; then  echo "\n\t No directories identified for specified pattern. No action taken.\n Bye!\n" ; exit 0 ; fi

echo "\n Starting directory move ..."

#########
#more "${TMP}.dirsToMove"
#exit 0
########

###
### Doing the action direction without a specific visual review could corrupt
### the HOME directory to point of unusability if the wrong directories are acted upon.
###
### Save all action commands into a batch file for manual visual review
### to confirm sanity of actions identified before applying.
###
rm -f "${BATCH}"

while read DirToMove
do
    if [ -n "${DirToMove}" ]
    then
        new=`echo "${DirToMove}" | eval sed \'s\+${SEARCH_ROOT}/\+\+\' `
        echo "mv -fv \"${DirToMove}\" \"${RELOCN_DIR}/${new}\" " 2>&1 | awk '{ printf("%s\n", $0 ) ; }' >>"${BATCH}"
    fi
done <"${TMP}.dirsToMove"
if [ ! -s "${BATCH}" ] ; then  echo "\n\t No directories identified for specified pattern. No action taken.\n Bye!\n" ; exit 0 ; fi

echo "\n Directory move BATCH file was created:\n"
cat "${BATCH}" | awk '{ printf("\t %s\n", $0 ) ; }'
echo ""
wc -l "${BATCH}"


exit 0
exit 0
exit 0
Eric Marceau
  • 368
  • 1
  • 10