2

I have a lot of mp3 files in a directory with different digits and spaces combination as file names. when I run ls | grep ^[0-9][0-9]" -" I get the output as below

01 - Hotel California.mp3
02 - Heartache Tonight.mp3
03 - The Long Run.mp3
04 - One Of These Nights.mp3

but when I do the same with a for loop for i in ``ls | grep ^[0-9][0-9]" -"``;do echo $i; done I get the same output but in a different format

01
-
Hotel
California.mp3
02
-
Heartache
Tonight.mp3

why is it so? how do I improvise it? what I am trying to do is replace 01 - with Eagles -

san1512
  • 914
  • 1
  • 9
  • 16

7 Answers7

3

See http://mywiki.wooledge.org/BashFAQ/001 for what you're doing wrong. You have to be careful about using the output of a command with spaces in it, to make sure you don't use it in a context where it will be word-split.

In your specific example, you don't actually need a regex. Shell glob expressions will do the trick.

for i in [0-9][0-9]' -'*; do
    mv "$i" "${i// /_}";  # / at start of pattern = all matches.
done

Or more usefully for messing around with filenames:

  • prename (perl-based renamer, packaged in Debian/Ubuntu in the perl package)
  • mmv 'foo*.mp3' 'bar#1.mp3'

So your goal of replacing track numbers with Eagles would be:

prename 's/[0-9]+ -/Eagles -/' *.mp3

Or tack Eagles - onto the front of every filename.

prename 's/^/Eagles - /'  [0-9]*.mp3  # or:
prename '$_ = "Eagles - " . $_'  [0-9]*.mp3  # you aren't limited to the s// operator, it really does eval as perl code.

Notice how you can use shell globs to select files to run prename on, so your actual pattern doen't have to avoid matching filenames you can filter other ways. If you want to use / in your pattern (to move files to a subdirectory, for example), I suggest using s{pat}{repl} syntax. So much nicer than a sed one-liner that turns into a forest of \/.

There's a shopt -s extglob bash option, but you're probably better off just using regexes instead of that, unless you're writing shell scripts for max efficiency. (e.g. programmable completion functions that run every time someone presses tab).

Community
  • 1
  • 1
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
2

File names with spaces in them cause problems when dealing with loops. You can deal with this in a few ways:

  1. Use while read:

    ls | grep -E "^[0-9][0-9] -"| while read line do mv "$line" new_name done

  2. Use find -print0 and perl: The command below will give the output you are looking for:

    find ./ -print0 | perl -0 -n -e 'print "$_\n" if /[0-9][0-9] -/;'

    Do you need help changing the filenames as well?

  3. Changing the IFS variable to only contain a newline or something similar. To learn more about this, you can read this article

gymbrall
  • 2,063
  • 18
  • 21
  • it is giving me all the combinations san@Ub1:/media/san/Media/test/tmp$ `find ./ -print0 | xargs -n1 -0 grep [0-9][0-9]" -" grep: ./: Is a directory Binary file ./008 Whitesnake - Here I Go Again.mp3 matches Binary file ./01 Hey You (2011 - Remaster).mp3 matches Binary file ./01 How Deep Is You Love.mp3 matches` – san1512 Aug 01 '15 at 19:32
  • sorry, my brain is tired, it's grepping the contents. I updated my answer. Look at this link about using perl to do the rename: http://www.perlmonks.org/?node_id=632437 – gymbrall Aug 01 '15 at 19:46
  • @san1512 I'd probably go with the while read option as it's designed to process data one line at a time. – gymbrall Aug 01 '15 at 19:53
  • Another excellent link for why `for i in $(cmd)` is no good: http://mywiki.wooledge.org/BashFAQ/001 – Peter Cordes Aug 01 '15 at 19:58
1

This was mentioned in another answer, but here it is with much less surrounding text:

$ rename 's/^\d\d - /Eagles - /' *.mp3

Don't reinvent the wheel when someone more experienced than you has already done it, much more cleanly than you have.

msw
  • 42,753
  • 9
  • 87
  • 112
0

EDIT: try awk only approach:

ls <music dir> | awk '/^[0-9][0-9] - .+\.mp3/{$1="Eagles"; print $0}'
isosceleswheel
  • 1,516
  • 12
  • 20
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/84898/discussion-between-isosceleswheel-and-san1512). – isosceleswheel Aug 01 '15 at 19:39
  • Thanks. I got the same output with `ls |grep ^[0-9][0-9]" -" |sed 's/^[0-9][0-9]\s-\s/Eagles - /g'` but it is not helping in renaming the files. As the loop is not giving the desired output – san1512 Aug 01 '15 at 19:47
  • If you system supports awk, there should be no need to use a for-loop. The regex is the first part of the awk command and the replace is the second part. Its default behavior is to loop through lines, so it will interpret each result from the `ls` as a line. – isosceleswheel Aug 01 '15 at 19:56
0

A for in Bash interprets every word as one "object" in a loop. To avoid this you have to put your command in quotes

➜  songs  touch "01 - foo"
➜  songs  touch "02 - bar"
➜  songs  touch "03 - baz"
➜  songs  for SONG in "$(ls | grep -E "^[0-9][0-9] -")"; do echo $SONG; done;
01 - foo
02 - bar
03 - baz

whereas:

➜  songs  for SONG in $(ls | grep -E "^[0-9][0-9] -"); do echo $SONG; done;
01

foo
02

bar
03

baz

edit: works for zsh, but not for bash. here the bash version:

~/tmp/songs$ for SONG in $(ls | grep -E "^[0-9][0-9] -"); do echo $SONG; done;
01
-
foo
02
-
bar
03
-
baz
~/tmp/songs$ IFS=$(echo -en "\n\b")
~/tmp/songs$ for SONG in $(ls | grep -E "^[0-9][0-9] -"); do echo $SONG; done;
01 - foo
02 - bar
03 - baz
Mario
  • 1
  • 1
  • 3
0

Maybe I'm wrong, but guess you are planning to do some renaming or other thing with them.

If you use |xargs after the "ls |" that may help you with the right parameters. But even here I would suggest to go with "ls -1" option (yes that is a number 1 there, each dir/file listed in 1 column. Much better to find out where 1 file name starts where the other ends from commands perspective)

For reading a whole line at once into a loop, you have the already mentioned option: chagning the IFS variable, then once you've done, you change it back. OR there is an other elegant way to deal with space:

ls -1 *.mp3|while read line;do echo $line ;done
zolo
  • 444
  • 2
  • 6
0

Finally I did it with the below command. Thanks for all the inputs

for i in [0-9][0-9]' -'*; do 
    mv $i `ls $i | grep ^[0-9][0-9]" -" | \
    sed 's/^[0-9][0-9] - /Eagles - /g'`
done
msw
  • 42,753
  • 9
  • 87
  • 112
san1512
  • 914
  • 1
  • 9
  • 16