1

This question already has an answer here:

let's say I have a directory with the following files/directories:

google
apple
mozilla foundation   # a file with spaces
browsers

So I would like to move files into browsers directory. Here is a script I wrote:

for d in $(ls); do
    if ! [ "$d" == "browsers" ]; then
        mv "$d" "browsers"
    fi
done

Then google and apple went to browsers but I got:

mv: cannot stat 'mozilla': No such file or directory
mv: cannot stat 'foundation': No such file or directory

Obviously, the problem is spaces in a variable. What's the proper way to deal with this?

Of course I believe there should be a more elegant one-line command to do this, but I would like to know how I should use variables with spaces.

marked as duplicate by muru, jasonwryan, Jeff Schaller, Archemar, Stephen Rauch Aug 11 '17 at 12:36

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

4

Do not ever use:

for d in $(ls)

Use this instead:

for d in *

Two problems, among others, with the use of $(ls) are that the shell subjects the results of $(ls) to word splitting and pathname expansion. In your case, it was word splitting that caused mozilla foundation to turn into mozilla and foundation.

For a lengthier discussion of the reason not to use $(ls), see "What does $(ls *.txt) do?"

If you need to account for the case where there is no non-hidden file in the directory (the case where for i in $(ls) may look better as it doesn't do any pass in the loop as opposed to one pass in the loop with * as $i in for i in * (except in zsh)), you'd want to tell the shell to not expand to anything for non-matching globs:

  • zsh: for i in *(N)
  • ksh93: for i in ~(N)*
  • bash4.4+: f() { local -; shopt -s nullglob; for i in *; ...; done; }; f (the point being to use the nullglob option locally (see also the failglob option for a behaviour similar to zsh's default one)).
  • yash: set -o nullglob (and reset afterwards, there's no local scope for options in yash as far as I know)
  • in other shells, you can always add a [ -e "$i" ] || [ -L "$i" ] || continue in the loop to check for the file's existence.
  • Oh, I didn't think that way. Thank you. So is there no way to change mozilla foundation to mozilla\ foundation or "mozilla foundation"? – Bumsik Kim Aug 11 '17 at 5:59
  • 1
    @BumsikKim there might be, but why break and reassemble when you can get it unbroken? – muru Aug 11 '17 at 6:01
  • You're welcome. And, yes, there are many ways to manipulate strings like mozilla foundation but, if you use the above for f in *, you won't need to manipulate it: it will just work. Or, were you looking for some other use? – John1024 Aug 11 '17 at 6:01
  • @John1024 I think that's enough. I was thinking the solution I was looking for might be useful in other potential cases but it was just a wild thought – Bumsik Kim Aug 11 '17 at 6:07
  • @BumsikKim Very good. Generally, the key is to avoid word splitting in the first place. (Once a file name is split, you'll never really know how to reassemble --- one space or two or tabs or ...) Fortunately, there are a variety of good techniques that avoid those pitfalls. – John1024 Aug 11 '17 at 6:29
1

Do not parse ls result, and beside of John1024's answer you could use find.

LC_ALL=C find . ! -name . -prune ! -name '.*' -type f -exec mv -t /path/to/dest {} +

(here assuming GNU find for the -t option, skipping hidden files like ls or a * glob would and excluding non-regular files. Note that the list is not sorted (contrary to with ls or *). LC_ALL=C is needed to properly skip any file whose name starts with . even those that don't contain valid characters in the user's locale. That affects the language of error messages though).

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