xargs, shell instances, execution order and bash one-liners

I must confess that i have an addiction to writing one-liners. Probably a bad habit i picked from hanging around code-golfers. Afterall, i am lazy and i prefer a quick and dirty solutions. But that is only part of the appeal. Apart from serving as a great party trick, occasionally, one-liners made me ponder questions that i normally would not have considered.

Let me start with a common example. I received a request to help in renaming some pictures. The whole gallery added up to a few thousand pictures and the filenames had underscores ( _ ) in them to seperate location and other details.  i was asked to remove the first part of the name before the underscore. Normally this a simple job for a for-loop as shown below.

for f  in  $(ls) ;  do
     mv  $f   $(echo  $f  |  cut  -d'_'  -f2)
done

 

But there are two powerful tools, bash pipe (|) and xargs and a familiarity with them would greatly simplify longer codes.  So Lets go through the series of steps building up along the way leading to the oneliner.

Obviously, the first step would be to list all the files. The easiest option is to use ls

 

Now the second part of the problem is to extract the part of the filename that appears after an the first underscore. For example one of the files is blautal_xxx_xxx.jpg. As our output we need xxx_xxx.jpg. There is a combination of linux command for it.

 

echo  $filename  |  cut  -d'_'  -f2

 

Now all that is required is to connect both these commands in a such a way that $filename variable which appears in second-part obtains its value from first-part.

The bash pipe as the name says is ideal to pipe the output of one command as an input for another. And xargs is ideal to break the list of filenames produced by ls into individual filenames and store it to a variable of our preferred choice.

| xargs -n1 -I{}

The -n1 option as per man pages limits max arguments to be passed to 1 and -I{} makes {} as the replacement string or a place holder. So naively thinking that the combination of the above three is all that is needed.

ls | xargs -n1 -I{} mv {} $( echo {} | cut -d'_' -f2 )

Except the output received would be

mv: 'blautal_xxx_xxx.jpg' and 'blautal_xxx_xxx.jpg' are the same file

 

Now time to wear the detective hat (in the comfort of our basement, ofcourse). Clearly, the order in which bash executed the above sequence and the expected order are different. Lets investigate the order in which bash executed. Setting

set -x

 

shows more details . Now we see the following interesting details.

 

+ ls

++ echo {}

+++ cut -d'_' -f2

++++ xargs -n1 '-I{}' mv '{}' '{}'

 

The first line is nothing unexpected. Second and the third line shows that bash executed the second part of our command before passing them to xargs, clearly not the expected sequence. Putting it in another way, the shell expansion happended before being passed to xargs. The resolution: Let xargs spawn a new shell instance and pass the value to the new shell and execute the commands cut and mv in the new shell. With this knowledge the following one-liner does the job.

ls | xargs -n1 -I{} bash -c ' mv {} "$(echo {} | cut -d'_' -f2)" '

 

 

 

Add new comment

The content of this field is kept private and will not be shown publicly.

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.
Blog tags