2

I'm trying to rename several files. So I need those file names first. I'm using:

for FILE in $(find . -type f -name "*.flv" -exec basename {} \; ); do
    echo "$FILE"
done

When I try just the find command, it returns the number of files correctly, but when Im using the for, I was expecting that ARQ would contain the entire name of a single file, but instead, it returns splited words of the filename. So how can I get the entire name, not just separated words of it?

Thur Brum
  • 53
  • 1
  • 8
  • 1
    add a language tag (bash script?) – M.M Oct 20 '15 at 03:00
  • Have a look at [this question](http://stackoverflow.com/questions/8677546/bash-for-in-looping-on-null-delimited-string-variable). The answer there shows a loop over files found by find, separated by the null character (`-print0` option in find). –  Oct 20 '15 at 03:36
  • Just `find . -type f -name "*.flv" -exec basename {} \;` already does what you want. – tripleee Oct 20 '15 at 04:23
  • You might find [this article](http://redsymbol.net/articles/unofficial-bash-strict-mode/), namely the section on "Setting IFS", informative. – dimo414 Oct 20 '15 at 04:51

4 Answers4

3

There are several ways to get that to work. The simplest is to use find's exec fully:

find . -type f -name "*.flv" -exec bash -c 'f=$(basename "$1"); printf "%s\n" "$f"' _ {} \;

In other words, you can put complex scripts in the -exec clause if you like.

As a second choice, consider this loop:

find . -name '*.flv' -print0 | while IFS= read -d '' -r file
do 
   f=$(basename "$file")
   printf "%s\n" "$f"
done
John1024
  • 109,961
  • 14
  • 137
  • 171
  • Process substitution is an option `while read -r line; do f=$(basename "$line"); echo "$f"; done < <(find . -name '*.flv')` – David C. Rankin Oct 20 '15 at 04:15
  • That's a [useless use of `echo`](http://www.iki.fi/era/unix/award.html) -- `basename` already prints its result to standard output; capturing it in a variable so you can `echo` that variable is just ... clumsy. – tripleee Oct 20 '15 at 04:22
  • @tripleee If the goal of the OP was to echo file names, _then you would be absolutely correct_. I think that, if you read the question carefully, it is clear that the basename-echo thing is merely a stand-in for a more complicated script that he intends to write (to rename files). The question he asks here,though, is limited to how to handle file names with spaces. I kept the two statements because they made it clear how a more complex script could be written using these techniques. – John1024 Oct 20 '15 at 04:33
  • @John1024: your second attempt need GNU or BSD find and will fail with filename start with dash or contain escaped sequences. The use of `basename` is also useless, just using parameter expansion. – cuonglm Oct 20 '15 at 04:39
  • @cuonglm `find` will return file names with a path, so they will not start with a dash. Which escaped sequences would be a problem here, for example? You mean escapes interpreted by (some dialects of) `echo`? – tripleee Oct 20 '15 at 04:41
  • @tripleee: That's the `$f` in `echo "$f"`, it's result of `basename`. Yes, some `echo` implementation will be interpreted escape sequence. Example with `bash`, try `env BASHOPTS=xpg_echo bash -c 'echo "\0101"'` – cuonglm Oct 20 '15 at 04:41
  • @cuonglm Yes, you are right. I often forget that `echo` has [so many problems](http://unix.stackexchange.com/questions/65803/why-is-printf-better-than-echo). Answer updated to use `printf`. – John1024 Oct 20 '15 at 05:36
2

Using for loop with the result from Command Substitution without double quote causing the result to break on space, tab and newline by default (that's IFS default value).

POSIXly, you don't need anything other than find and an inline-script:

$ find . -type f -name "*.flv" -exec sh -c '
  for f do
    printf "%s\n" "${f##*/}"
  done
' sh {} +

With GNU find, you don't need the inline-script:

$ find . -type f -name "*.flv" -printf '%f\n'
cuonglm
  • 2,766
  • 1
  • 22
  • 33
1

Looking at the title of the question: avoiding splitting a string using for in:

Do not use the IFS field separators in the loop:

:~> a="sdad asd asda  ad
> fdvbdsvf
> dfvsdfv
> 4"

:~> for s in $a; do
       echo "== $s =="; 
    done
== sdad ==
== asd ==
== asda ==
== ad ==
== fdvbdsvf ==
== dfvsdfv ==
== 4 ==

:~> (IFS=; for s in $a; do
       echo "== $s =="; 
     done)
== sdad asd asda  ad
fdvbdsvf
dfvsdfv
4 ==

I used round brackets for the last command, so that the changed value of IFS is limited to that subprocess.

Walter A
  • 19,067
  • 2
  • 23
  • 43
0

Instead of using find, use rename command which is designed to rename multiple files.

For example:

rename 's/foo/bar/' **/*.flv

which would replace foo in filename into bar in all *.flv files recursively. If your shell supports a new globbing option (such as Bash 4.x or zsh), make sure the option is enabled by shopt -s globstar.

Or if you're using find with a loop, you can use:

  • -print0 when piping to external programs such as xargs (with -0),
  • use -exec cmd to run command directly on the file ({})
  • use -execdir cmd to execute command in the directory where the file is present
kenorb
  • 155,785
  • 88
  • 678
  • 743