Skip navigation

Tag Archives: xargs

This one was eluding me for a while, what I simple wanted is to use cp (copy) with xargs to duplicate files in a different destination. What I had used before is the command copy in combination with find, via the exec argument like this:

find . -maxdepth 1 -name 'in*.css' -exec cp '{}' ~/src/Report/geo_portal/t/tmpl/src/css/ \;

But I wanted to use xargs, since I pretty much like its elegance and possibilites. The example below is a real one I used in BSD 7:

find . -name '*.html' | xargs grep -l 'Datasets' | xargs -J % cp -rp % ~/src/Report/playground/t/tmpl/src/css/

The explanation lies in the xargs manual, albeit a bit obscure:

-J replstr
If this option is specified, xargs will use the data read from standard input to replace the first occurrence of replstr instead of appending that data after all other arguments. This option will not affect how many arguments will be read from input (-n), or the size of the command(s) xargs will generate (-s). The option just moves where those arguments will be placed in the command(s) that are executed. The replstr must show up as a distinct argument to xargs. It will not be recognized if, for instance, it is in the middle of a quoted string. Furthermore, only the first occurrence of the replstr will be replaced. For example, the following command will copy the list of files and directories which start with an uppercase letter in the current directory to destdir:

/bin/ls -1d [A-Z]* | xargs -J % cp -rp % destdir

Note: When I wanted to use it in my Cygwin shell I had to replace the option “-J” with “-I”. In this example I copied every image lighter that 3000k into other folder.

ls -ltra | awk '{print $5," ",$9}' | awk '$1 < 3000' | awk '/gif|png|jpg/ {print $2}' | xargs -I % cp -rp % /web/R/images/

Again using xargs comes in handy to speed up the process of committing a given list of files to the repository.
First let’s preview the command which will save us to type every file name (and their paths)

svn status |   awk '/^M/ {print $2," "}' | xargs echo svn ci -m \"Checking into the repository my last modified files\"

and once it’s been carefully examined, let it run by piping it to bash

svn status |   awk '/^M/{print $2," "}' | xargs echo svn ci -m \"Checking into the repository my last modified files\" | bash

Today I needed to massively clean lines containing x’s in front some css declarations in a group of files (context: this is some sort of trick I frequently use to cancel instead of deleting a property in a declaration while live editing css with the indispensable webdeveloper plugin for Firefox. practical as it the trick, if you forget to erase it immediately the x yields error on the www3 validation later on)

I could easily filter up the desired files and even remove the line doing something like this:

ls | awk '/.css$/' | xargs sed -i.bk -e 's/^ *x.*$//g'

The problem was that I did not want to simply erase the line without it’s corresponding line break, as will be the result here (the explanation is that sed by default, operates on single lines, stripping the line break from the stdin and appending it back after doing the substitution)

The simple way I found around it was to filter all lines matching the regex (outputing the rest to the file) instead of performing the substitution on every single line which would leave and empty line.

Like this:

ls | awk '/.css$/' | xargs sed -i.bk -e '/^ *x.*$/d'

I figured how to discard lines from this excellent collection of sed oneliners It shows two alternatives.

# print only lines which do NOT match regexp (emulates “grep -v”)
sed -n ‘/regexp/!p’ # method 1, corresponds to above
sed ‘/regexp/d’ # method 2, simpler syntax

Here’s the complete online command used at the shell command line (which I ran in emacs by the way)

find . -type f -exec grep -i '^ *x' /dev/null {} + | awk '!/svn|htdocs/' | cut -c 3- | awk '!/^#/' | awk -F ':' '{print $1}' | awk '/.css$/'| xargs sed -i.bk -e '/^ *x.*$/d\
'

To massively replace text from a group of files:
// this searches all html files (not into htdocs) which contain tab = “DATs” and modifies that to tab = “Datasets” in one pass
// it’s important to notice that in the process they leave a backup copy of each one with the -e appended to the extension so bla.html will be modified but an extra bla.htm-e will exist holding the content of the old file

find .  -name '*.html' | awk '!/htdocs/' | xargs grep -l 'tab = "DATs"' | xargs sed -i -e 's/tab = "DATs"/tab = "Datasets"/g'

Note1: Actually the -i option takes whatever it’s after to create the extension of the backup to create. Use -ei if you want nothing or -e -i.bak to make it more standard.
Note2:
this worked better later on

find . -type f | xargs grep "url *=> *'/DAT_introduction.html'" | awk '!/svn|htdocs|blib|README|.bk/' | awk '{print $1}' | awk -F ':' '{print $1}' | xargs perl -pi -e "s#url *=> *'/DAT_introduction.html'#url => '/datasets.html'#g"

There are many things different:
awk cleans clean up the fields so to have only the path and file name printed
awk also helps here filtering out many option I don’t want to list and they are put together with the simple clause “|” (or) in the regex

instead of sed I used perl this time (sort of more familiar), notice that I used a different character “#” for the substitution delimiter, since I don’t want to escape “/” in the regex