I'm trying to create a bash function script that will allow me to create multiple directories with the names below. I'm also trying to organize files by extension into their corresponding folders (i.e. .jpg into pictures, .doc into documents, .gif into media, etc). The first part is fine it's the second half after the directories are made that confuses me.

    echo "Creating directory categories"

    function make_folder 
        cd -; cd content; sudo mkdir ./$1

    make_folder "documents"
    make_folder "other"
    make_folder "pictures"
    make_folder "media"

    echo "Directories have been made"; cd -

    ext="${filename##*.}" #set var ext to extension of files

    find ./random -name | #find and list all files in random folder
                          #pipe results of find into if statement

    if ext == ["jpg"; "jpeg"; "png"] #move ".jpg", etc to new destination
           mv /path/to/source /path/to/destination

    elif ext == [".gif"; ".mov"] #move ".gif", etc to new destination 
           mv /path/to/source /path/to/destination
    else                         #move other files into to new destination
           mv /path/to/source /path/to/destination
  • Ok on top of my head for f in jpg mov; do files=(*."$f"); if [[ "${files[@]#*.}" == *jpg* ]]; then mv "${files[@]}" jpgdir/; else mv "${files[@]}" movies/; fi; done This should get you going. If you prefer to use switch you can do so. Try to understand this one liner and see if it fits your situation – Valentin Bajrami Apr 13 '16 at 18:50
  • @val0x00ff using that would I still use ${filename##*.} to get the extension or leave it out altogether? – lettda Apr 13 '16 at 19:05
  • @letda, leave it out. You don't have to specify each file separately. The if [[ "${files[@]#*.}" == *jpg* ]]; .... will loop through the array and compare the extension. If the extension matches, then action is taken. – Valentin Bajrami Apr 13 '16 at 19:08

From the top of my head I would use the 'case' statement in the end.


case "$FILE" in
        mv "$FILE" to where you want it
        mv "$FILE" to where you want it
        echo "Unmanaged file type: $FILE, skipping"

...however you need to wrap it in a loop container, and you seen determined to use find, and then it would perhaps be appropriate with;

 find <your stuff> | 
     while read FILE
         ...case statement goes here

Just my $0.02

Cheers! / Daniel

  • I'm not opposed to getting rid of find at all. I'm just new to Bash and i understand what it does already lol. Can you quickly explain what "$FILE" means? I'd like to understand rather than copy/paste – lettda Apr 13 '16 at 20:13
  • We were all new to Bash at one time :). FILE is just a variable name, could be anything. In the assignment (ie. read FILE) you use it without $, then when you use/read it, you need to prepend the $. Then also if for instance your filename would be "a movie.mov" the space would cause havoc as the case statement would become case a movie.mov in, so therefore you don't use case $FILE in but case "$FILE" in to keep the contents as a single string, i.e. case "a movie.mov" in – Daniel Apr 14 '16 at 11:42

Your make_folder function is needlessly complicated: all these current directory changes are just making your life more complicated. On top of that, you don't need sudo: why would you create the directories as root? Just call mkdir.

mkdir documents other pictures media

If you want the script to work even if the directories already exist, use mkdir -p:

mkdir -p documents other pictures media

If you want to use find , don't make it print the names and then parse them. Make find execute the code you want with -exec. To do more than invoke a simple program, invoke a shell. With <code>sh -c '<em>shell code</em>', the next argument to sh after the shell code is available in the shell code as $0. Always put double quotes around varaiable substitutions. In the shell snippet, the natural way to discriminate by extension is with a case construct on the file name. I use the parameter expansion construct ${0##*/} to get the base name of the file (with everything up to the last slash removed). The test [ -e "$destination_file" ] tests if the destination file already exists, to avoid overwriting a file by the same name that was moved from a different directory.

find random -exec sh -c '
  case "$0" in
    *.gif|*.png|*.jpg) destination_directory=pictures;;
    *) destination_directory=other;;
  if [ -e "$destination_file" ]; then mv "$0" "$destination_file"; fi
' {} \;

An alternative approach, which may be a little slower if you have a huge amount of files but is a lot simpler to write, is to copy the files destination by destination. On Linux, thanks to GNU utilities, it's as simple as

find random \( -name '*.gif' -o -name '*.png' -o -name '*.jpg' \) -exec mv -n -t pictures {} +

and likewise for other target directories. Using + rather than ; at the end of find -exec speeds things up a little by invoking mv in batches.

But for what you're doing, in fact, in bash or zsh, you don't need find. Use the ** wildcard for recursive directory traversal.

shopt -s globstar # necessary in bash to enable **
mv -n random/**/*.gif random/**/*.png random/**/*.jpg pictures/

Note that I assume that you want to put all the files directly under the target directories. If you want to reproduce the directory hierarchy, that's a different matter, and different tools would be appropriate.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.