1

I'm trying to execute this bash script that loops over files (that have spaces in the name) in the current directory and creates a new folder with the first character of the file (if not already created) and moves that file to the folder. This is my code:

for i in `/bin/ls | xargs`
do
    dir=`echo "$i" | cut -c 1 -`
    mkdir -m777 -p "$dir"
mv "$i" "$dir"
done

The problem with this seems to be that it treats the each word in the file as a separate file, so although it creates the folder correctly , it can't move the file to that folder because the name of the file that it looks for is only the first word of the actual file. I looked at other answers to similar questions but this is the closest I've been able to get.

EDIT: I replaced " for i in /bin/ls | xargs " with " for i in * " as @steeldriver suggested, and although it fixed my original problem, I'm getting errors like these:

mv: cannot move '`' to a subdirectory of itself, '`/`'
mv: invalid option -- ' '
mv: missing destination file operand after '-'
mv: invalid option -- '.'
mv: invalid option -- ')'
mv: invalid option -- '+'
mv: cannot move ''$'\340' to a subdirectory of itself, ''$'\340''/'$'\340'
mv: cannot move ''$'\303' to a subdirectory of itself, ''$'\303''/'$'\303'
mv: cannot move ''$'\305' to a subdirectory of itself, ''$'\305''/'$'\305'
mv: invalid option -- '1'

I think some of these files may start with non-ascii characters (I can't view the contents because there are too many files). Is there a work around to handle these cases?

Logan
  • 121
  • 3
  • 1
    `for i in *; do ...` - see [Bash Pitfall #1](https://mywiki.wooledge.org/BashPitfalls#for_f_in_.24.28ls_.2A.mp3.29) – steeldriver May 07 '19 at 21:48
  • Welcome to Unix & Linux! It is generally a [really bad idea](http://mywiki.wooledge.org/ParsingLs") to parse the output of `ls`. You should probably look into either using `find` or simple shell globbing to get your list of files to process. Extensive further reading on the subject can be found [here](https://unix.stackexchange.com/questions/128985/why-not-parse-ls"). – DopeGhoti May 07 '19 at 21:55
  • Also, this will fail if you already (for example) have a file called `t`. – DopeGhoti May 07 '19 at 22:03

2 Answers2

2

To loop over files with spaces in their names, the shell is plenty, no need to call ls:

for    i in *                   # * replaces the complex (and unquoted) `/bin/ls | xargs`
do
       dir=${i%"${i#?}"}        # replaces the slow subshell `echo "$i" | cut -c 1 -`

       echo "$i"                # just to show that an * is enough (and accepts spaces).
done

And to process each file listed (which include directories) you should check that the filename is a file (not a directory) and also check if the directory doesn't exist before creating it.

for i in *
do
    if [ -f "$i" ]; then
        dir=${i%"${i#?}"}
        if [ ! -d "$dir" ]; then
            mkdir -m777 -p "$dir"
        fi
        mv "$i" "$dir"
        if [ "$?" -ne 0 ]; then
            echo "An error occurred moving file \"$i\" to dir \"$dir\""
        fi
    fi
done
0

With GNU Parallel it looks like this:

parallel 'mkdir -p {=s/(.).*/$1/=}; mv {} {=s/(.).*/$1/=}' ::: *

(Edit: Just noted you are asking for files - not dirs. / is removed).

Ole Tange
  • 33,591
  • 31
  • 102
  • 198
  • When I try this, I get the error: mv: cannot move '*/' to a subdirectory of itself, '*/*' – Logan May 07 '19 at 22:13