1

I have a fodler that contains file names that have spaces in them.

I have made a script that replaces the spaces and creates the new file name with _ instead of " " (space) and ran the mv command in the for loop.

This has not worked in script and giving me the below message.

   Usage: mv [-I] [ -d | -e] [-i | -f] [-E{force|ignore|warn}] [--] src target
   or: mv [-I] [-d | -e] [-i | -f] [-E{force|ignore|warn}] [--] src1 ... srcN directory

But if i debug the shell script and extract the mv command individually and run it,it does work fine without any issues.

The command that is generated looks like

   mv "abc  ddd eee.txt" abc_ddd_eee.txt

Could you please advise why the command when typed on unix box worked fine and not via script ?

Entire script:

     for i in * 
      do 
       v1=$(echo $i|sed -e 's/^/"/g' -e 's/$/"/g' )
       v2=$(echo $v1|sed 's/ /_/g'|
             awk -F "[-.]" '{print $2,"_",$1,".",$3}'|
             sed 's/ //g'|
             sed -e 's/^_//g' -e 's/"$//g'|
             sed 's/"//2' )        
       mv $v1 $v2

      done
William Pursell
  • 204,365
  • 48
  • 270
  • 300
user2075017
  • 457
  • 4
  • 11
  • 23
  • 2
    What does your shell script (the relevant part, anyway) look like? – chepner Jul 28 '15 at 16:13
  • Actually my script does a lot.for i in * do v1=`echo $i|sed -e 's/^/"/g' -e 's/$/"/g'` v2=`echo $v1|sed 's/ /_/g'|awk -F "[-.]" '{print $2,"_",$1,".",$3}'|sed 's/ //g'|sed -e 's/^_//g' -e 's/"$//g'|sed 's/"//2'` mv $v1 $v2 done – user2075017 Jul 28 '15 at 16:15
  • Like I said, show the *relevant* part; we can't debug a script we can't see. – chepner Jul 28 '15 at 16:15
  • 1
    I suspect the problem is that you're putting quotes in a variable. Quotes aren't processed when expanding variables. – Barmar Jul 28 '15 at 16:16
  • See http://stackoverflow.com/questions/13365553/setting-an-argument-with-bash – Barmar Jul 28 '15 at 16:16
  • try using '' single quotes around name with spaces. –  Jul 28 '15 at 16:16
  • @GRC Why do you think that would work differently from double quotes? The only difference between them is if there are variables or command expansions in the string. – Barmar Jul 28 '15 at 16:17
  • True,i suspected the same @Barmar and have echo'ed that mv command to another seperate file and tried to run that file explicitly.same issue – user2075017 Jul 28 '15 at 16:17
  • @Barmar,I couldnt get the link that you shared linked with my issue. – user2075017 Jul 28 '15 at 16:21
  • I have seen this happen where you would do a shell script with two spaces inside string literal, 2 spaces would collapse into one and it would not work. hen used single quotes you are telling shell do not touch this string. –  Jul 28 '15 at 16:23
  • The error message means that the command is seeing more than 2 arguments. It's not complaining that the file isn't found. – Barmar Jul 28 '15 at 16:27
  • @user2075017 If you want me to reopen the question, you need to post your code so we can show how to correct it. – Barmar Jul 28 '15 at 16:28
  • @user2075017 Post it in the question, not in a comment, so it can be formatted readably. – Barmar Jul 28 '15 at 16:29
  • Posted the code in readable format @Barmar – user2075017 Jul 28 '15 at 16:33
  • Understanding the difference between syntactic quotes and literal ones would go a long distance towards preventing this from happening in the future. – Charles Duffy Jul 28 '15 at 16:42
  • Also, all the messing with awk and sed (and echo) is completely unnecessary (and harmful; every time you run `echo $foo`, the value in `foo` is split on IFS, globs in the resulting strings are expanded, etc; `echo "$foo"` is better, but still much worse than doing using native string manipulation primitives). – Charles Duffy Jul 28 '15 at 16:43
  • ...re: "native string manipulation", see BashFAQ #100: http://mywiki.wooledge.org/BashFAQ/100. Alternately, you could describe in English what this mess of ugly code is supposed to actually accomplish, and we could suggest a better way to achieve that desired end. – Charles Duffy Jul 28 '15 at 16:45
  • BashFAQ #50 is also very relevant, in terms of the "why" behind your failure: http://mywiki.wooledge.org/BashFAQ/050 – Charles Duffy Jul 28 '15 at 16:45
  • (to explain the "why" a bit differently: Syntactic quotes are parsed *before* parameter expansion occurs; thus, quotes *within* an expanded parameter are treated as data, not syntax. This is actually a desirable characteristic for security reasons -- allowing data to be treated as syntax is the fast track to an injection attack). – Charles Duffy Jul 28 '15 at 16:52

2 Answers2

5

In native bash, with no external tools other than mv itself:

for i in *; do
    # change all spaces to underscores
    v2=${i// /_}

    # split into pieces on . and _ characters, and reassemble after 
    IFS='._' read -r first second rest <<<"$v2" && {
      v2="${second}_${first}.${rest}"
    }

    # remove any leading underscore
    v2=${v2#_}

    # rename
    mv -- "$i" "$v2"
done

This is vastly more efficient than setting up a new pipeline running sed and awk once for each filename processed.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • Thanks a lot for your valuable guidance – user2075017 Jul 29 '15 at 08:52
  • @Charles that's such a cool trick with `var=${a//b/c}`, upvoted already. Looks like I can stop abusing sed and awk for all substitutions. What is that operation called so I can look up the reference for more things to learn. Found it: [Parameter expansion](http://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html) – Donbhupi Jul 30 '15 at 06:02
  • 1
    @Donbhupi, *nod*; http://wiki.bash-hackers.org/syntax/pe is another good reference. Awk is actually quite a bit faster than bash for processing big streams, but if you're only doing one line at a time (or fewer than hundreds in total), the startup overhead is killer. Often, it makes sense just to write a text-processing script in pure awk, with no bash or sed involved. – Charles Duffy Jul 30 '15 at 14:53
4

There's no need to add quotes around the name, just quote the variables. Do:

for i in * 
do 
   v2=$(echo "$i" | sed 's/ /_/g' | awk -F "[-.]" '{print $2,"_",$1,".",$3}' | sed 's/^_//')     
   mv "$i" "$v2"
done
Barmar
  • 741,623
  • 53
  • 500
  • 612