1

I'm trying to rename all files that start with an "m" to be the same name except with the first character (or "m" in this case) stripped away.

My strategy is to:

  1. List all the files, with ls
  2. Filter for the ones I want, with egrep
  3. Generate the string I don't want next to the one I want, separated by a space, with awk, for example, mfoo foo
  4. Feed into xargs to mv mfoo foo

Some questions:

  • Is this a good strategy?
  • What is a better one?

I'm stuck on Step 3, below is how I've approached the problem.

I'm working in the following directory:

$ find .
.
./cat
./mbar
./mbaz
./mfoo

I'm able to quickly get 1-2:

$ ls | egrep '^m'
mbar
mbaz
mfoo

Step 3 is more difficult. I used gsub to generate the second string I want, but I'm not sure how to "stick it together with the original value separated by a space":

$ ls | egrep '^m' | awk '{ gsub(/^./, ""); print }'
bar
baz
foo

Step 4 by it's makes sense to me, although I'm not sure how to finish Step 3 so I can't finish it yet. Below is one example of how I think it should work:

$ echo mfoo foo | xargs mv
$ find .
.
./cat
./foo
./mbar
./mbaz

I think I'm close I just need to find out how to save the old value and print it next to the gsubed value. I've tried the following small example but it's not working:

$ echo mfoo | awk '
pipe quote> { old = $0 }
pipe quote> { new = gsub(/^./, "") }
pipe quote> { print $old " " $new }'
awk: illegal field $(mfoo), name "old"
 input record number 1, file
 source line number 4
  • How do I make a substitution to $0 but save the old value?
  • Why am I getting this error?
mbigras
  • 3,100

3 Answers3

8

This should handle the whole operation:

for file in m*; do mv "${file}" "${file#m}"; done

Before you run that, check things first with

for file in m*; do echo mv "${file}" "${file#m}"; done

This uses the m* glob for steps 1 and 2, then ${file#m} (which removes “m” from the beginning of ${file}) for step 3, and finally a loop for step 4.

To answer your AWK question, you could go about it this way:

echo mfoo | awk '{ print $0, substr($0, 2) }'

AWK variables don’t use $, that’s just for fields; that’s where your error is coming from: AWK understands $old as “the value of the field numbered according to the value of old”. It’s easier to read too if you place your commands in a single block (assuming they have the same pattern). Fixing your script gives

echo mfoo | awk '{ old = $0; gsub(/^./, ""); print old, $0 }'
Stephen Kitt
  • 434,908
  • Thank you for the response! Do you think it's not a good strategy to break up the tasks using separate programs and stick them together as part of a pipeline? I like it better because it's easier for me to think about. Also is this not a good time to use awk? I'm learning how to use it so I was excited to use it but I also thought it was an appropriate time to use it. – mbigras Apr 09 '17 at 21:31
  • 2
    Breaking up tasks is a good strategy, but you need to use the appropriate tools (and learning about all the tools is a never-ending task...). Parsing ls’s output is not a good idea, and using AWK for simple string processing is overkill. – Stephen Kitt Apr 09 '17 at 21:35
  • But do you think learning bash substring removal is worth it? The first line of the bash scripting guide: Bash supports a surprising number of string manipulation operations. Unfortunately, these tools lack a unified focus. Some are a subset of parameter substitution, and others fall under the functionality of the UNIX expr command. This results in inconsistent command syntax and overlap of functionality, not to mention confusion. It seems like starting off by just learning awk and sed for string manipulation is a better idea, no? – mbigras Apr 09 '17 at 23:56
  • 1
    Yes, I think it’s worth it, for two reasons. The first is that using another process has a cost (which can be amortised by running a single process over a stream of data). The second, and main reason is that passing strings around from the shell to other processes and back involves parsing those strings, and losing information about them. – Stephen Kitt Apr 10 '17 at 04:41
2
ls | egrep '^m' | awk '{ x=$0; gsub(/^./, ""); $0 = x " " $0 }1' | xargs -l -t mv

Posix-ly implementation is via the -L option to xargs as:

ls | egrep '^m' | awk '{ x=$0; gsub(/^./, ""); $0 = x " " $0 }1' | xargs -L 1 -t mv

ls | egrep '^m' | awk '{ x=$0; gsub(/^./, ""); print x, $0 }' | xargs -L 1 -t mv

Based on what I replied to your earlier query regarding xargs, we can put that learning to good use in this example.

I slightly modified your awk code: it preserves the original line ($0) since the gsub func is gonna clobber it. Then we put together the old & the new to get the line we want to send over to xargs which will then invoke mv with the right arguments to effect the rename.

  • What does the 1 (one) at the end of your awk program do? – mbigras Apr 10 '17 at 01:13
  • 1 will print the current line ($0) after all the modifs have happened. I've added an alternative to the 1 in the Answer section, you may want to check. –  Apr 10 '17 at 01:17
  • 1
    @mbigras awk's default action for a true condition is to print $0, so many awk commands end with a 1 as a shortcut to printing the line. – muru Apr 10 '17 at 01:19
  • Note that this is only an acceptable solution if you don’t care about your data. Since you like learning experiences I’ll let you figure out (or discover) why :-). – Stephen Kitt Apr 10 '17 at 04:42
  • Just for the record, gnu xargs man pages in Debian refers that -l option is deprecated and -L should be used instead :https://manpages.debian.org/testing/findutils/xargs.1.en.html – George Vasiliou Apr 10 '17 at 07:18
2

A better strategy is to use the rename command.

Note that the exact syntax of this commands is distro specific, consult the man page of your particular version of distro. On Ubuntu, for example, which has a Perl implementation of rename, you can use a regex:

rename -nono 's/^m//' m*

Note: remove the -nono flag to actually perform the renaming.

heemayl
  • 56,300
Lie Ryan
  • 1,228