1

I am writing a script to help me keep my files organized. Essentially, I would like the script to organize my files based on their extension. The current solution looks at everything in a given directory. This is a problem because folders with dots in their name get moved.

I wanted to know if there is an if statement that can be used to check if the directory is an actual directory or not. If it is indeed a directory, then the script should not move it.

Here is the code portion. I'm using a for loop to iterate through the files, this is based roughly off an old batch script I had in windows, so this may not be the best way to go. Please let me know if there is a better solution:

#!/bin/bash
for i in *.*; do
  filename=$(basename -- "$i")
  extension="${filename##*.}"
  extension="$extension"files
  mkdir -p "$extension"
  mv "$i" "$extension"
done

3 Answers3

2

I'd personally go with a find and a while loop as you can specify the file type with -type d/f for directory/file respectively

as a "one liner" from the directory you'll be running this from:

find . -maxdepth 1 -type f -name "*.*" | while IFS="" read -r line; do filename=$(basename -- "$line"); extension="${filename##.}.files"; mkdir -p "$extension"; mv "$line" "$extension"; done

Or in a script:

#!/bin/bash
find . -maxdepth 1 -type f -name "*.*" |
while IFS="" read -r line; do
    filename=$(basename -- "$line")
    extension="${filename##.}.files"
    mkdir -p "$extension"
    mv "$line" "$extension"
done

explanation

find . - finds files and directories from the current directory

-maxdepth 1 - only checks the current directory (2 would check the next level dirs too)

-type f - only matches files

-name *.* - any file with a "." in the name

I also changed the extension to .files in one variable declaration as I assume that's what you want. If you want say tmp.txt to be in a dir called tmp.txtfiles instead (like in your script currently), then just remove the "." like so:

extension="${filename##.}files"

EDIT:

So as @Kusalananda pointed out, looping over a find can be a bad idea. That being said, you can still use find for this, just using a temporary file to loop over instead:

#!/bin/bash
find . -maxdepth 1 -type f -name "*.*" > myTemporaryFile

IFS=$'\n' # in case any filenames have spaces in them

for line in $(cat myTemporaryFile); do
    filename=$(basename -- "$line")
    extension="${filename##.}.files"
    mkdir -p "$extension"
    mv "$line" "$extension"
done

rm myTemporaryFile
RobotJohnny
  • 1,039
  • 8
  • 18
2

I'll propose two other methods; one from Kusalananda's comment and one from another shell altogether.

First, bash (and other shells) have a simple test operator to see if something is a directory: [ -d something ]; you could use it like this:

#!/bin/bash
for i in *.*; do
  [ -d "$i" ] && continue
  # ... rest of the script
done

The above would check each "file" from the *.* wildcard to see if it is a directory; if it is, the test passes and the loop continues on with the next item (if any).

Alternatively, you could use zsh, which has extensive wildcard features, one of which is to limit the matches to certain types of files (regular files, directories, etc). You could use it like:

#!/bin/zsh
for i in *.*(.)
do
  # ... rest of your script
done

The special syntax of (.) is a "glob qualifier"; the parenthesis indicate that it's a qualifier, and the period says that we're looking for plain files. The result is that the for loop will only match files -- never directories.

Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
1

You can use -f in an if statement to check if it is a file:

if [ -f "${i}" ];then
  echo "${i} is a file."
else
  echo "${i} is not a file."
fi

-d can be used to determine if it is a directory and ! can be prefixed to inverse the logic, i.e. check if the variable is not a file:

if [ ! -f "${i}" ];then
  echo "${i} is not a file"
fi