2

I try to organize my files and my folders by a script. It creates the folder with the first letter of my file and moves it there.

For the very first time, the script does the job, but if I create new files and execute the script again, it creates a new subfolder in the folder, and so on.

It creates, for example, T/T/toto.txt.

My script:

for first in $(ls -1 | sed 's/^\(.\).*$/\1/' | tr '[a-z0-9]' '[A-Z0-9]' | uniq)
do
    mkdir tmp
    mv "$first"* tmp/
    lower=$(echo $first | tr '[A-Z]' '[a-z]')
    mv "$lower"* tmp/
    mv tmp/ "$first";
done
Kusalananda
  • 333,661
  • I guess you rerun this script in the same folder you run it before. Therefore it will find the previously created directories and will move them as well. – bey0nd Mar 01 '20 at 20:35
  • You probably should add #!/bin/bash as the first line. Won't fix the problem but makes it clear you've written a bash script and not a sh one. – Chris Davies Mar 02 '20 at 09:02

1 Answers1

6

Your script is picking up directories and non-directory files and moving these into subdirectories based on their names, even if they are the single-letter directories that we are sorting things into. That's how the new directories appear. Your code also needlessly uses sed and tr etc., to do something that the bash shell can do quicker and safer. Additionally, ls should never be used in scripts for iterating over filenames (see, e.g. Why *not* parse `ls` (and what to do instead)? for why).

Another issue is that if you have many thousands of files with names starting with the same character, then the mv "$first"* tmp/ command (or the second mv) may fail with an "argument list too long" error (see, e.g. Argument list too long for ls ; the issue applies to mv and all other external commands, not just ls).

An example script that works around these issues and that only uses functionality in bash apart from mkdir and mv:

#!/bin/bash

shopt -s nullglob # makes the loop not loop at all if there are no files

for name in *; do if [[ -d $name ]] && [[ $name == ? ]]; then # skip directories that have single character names continue fi

# get first character in filename
first=${name:0:1}

# compute destination directory name from this first
# character by uppercasing it
destdir=${first^}

# make that directory if it does not already exist
mkdir -p -- "$destdir"

# move the file into place (name collisions not checked for)
mv -- "$name" "$destdir"

done

The -- options in the calls to mkdir and mv protect against filenames that may start with a dash (see, e.g. What does "--" (double-dash) mean?).

The main difference with this script is that I'm looping over the names in the directory rather than over the first characters, as you do.

Kusalananda
  • 333,661