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?

3
  • 1
    for i in *; do ... - see Bash Pitfall #1 May 7 '19 at 21:48
  • Welcome to Unix & Linux! It is generally a really bad idea 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.
    – DopeGhoti
    May 7 '19 at 21:55
  • Also, this will fail if you already (for example) have a file called t.
    – DopeGhoti
    May 7 '19 at 22:03
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).

1
  • When I try this, I get the error: mv: cannot move '/' to a subdirectory of itself, '*/'
    – Logan
    May 7 '19 at 22:13

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.