Joseph O'Shea

Using xargs to move multiple files at once (on MacOS)

xargs

Background

I know xargs exists. I know it is powerful. But I've never really taken the time do sit down and figure out how it works to be able to use it regularly.

Today I ran into a situation that made me think "I know xargs can do this, but I don't know how to do it" and decided it was worth the time to at least figure out this one simple case.

Disclaimer: I am on MacOS and I noticed xargs on MacOS has some different behavior from Linux distributions. I am not an xargs expert, so I cannot promise this will work on other systems.

The situation

The situation was that I had a list of files in files.txt which I wanted to move from their current location to a different folder (./input)

So for example, if my files.txt was:

tags.njk
posts.md
util/foo.js
css/index.css

I wanted to write a single command which would read from that file and move each of those files to ./input, such that the file state is those files are moved to:

./input/tags.njk
./input/posts.md
./input/util/foo.js
./input/css/index.css

The solution

tl / don't care - the solution at the end

The final solution was:

<input.txt xargs -n1 -I{} mv {} ./input/{}

If you're curious, the discovery and learning process is described below.

The process

Of course, I turned to google. I wasn't worried about finding an example specifically moving files. I simply wanted a more general example:

how do I use xargs to read arguments, one arg per file, and execute the same command with each of those args?

So I crafted a google query which I thought would lead in the right direction:

xargs multiple commands one arg per line of file

The first StackOverflow post led me to this:

<a.txt xargs -d $'\n' sh -c 'for arg do command1 "$arg"; command2 "$arg"; ...; done' _

Credit to this answer here

I modified that to fit my needs. For the purpose of getting a proof of concept, my goal was to make my "command to execute" simply "echo" - I just want to print the arg back out. If I can run a command that prints back all the lines from my file, I know I can easily modify that command to do anything else with those args.

So I modified it to:

<files.txt xargs -d $'\n' sh -c 'for arg do echo "$arg"; done' _

That didn't work! Turns out MacOS version of xargs (at least the one have) does not support -d

<files.txt xargs -d $'\n' sh -c 'for arg do echo "$arg"; done' _

xargs: illegal option -- d
usage: xargs [-0opt] [-E eofstr] [-I replstr [-R replacements] [-S replsize]]
[-J replstr] [-L number] [-n number [-x]] [-P maxprocs]
[-s size] [utility [argument ...]]

sidebar: I do believe there is a way to install a different version of xargs on MacOS so that you can use the -d flag. I chose not to go down this route because I wasn't in the mood to go installing new things. Plus I was curious to learn how to do it on this Mac version specifically. However, I do think the option of installing a different version of xargs is a totally valid approach.

So now I thought "I need a MacOS specific learning resource". So I googled:

xargs macos tutorial

The first few links didn't seem to have what I needed, but a few links down I found this post which had this useful example:

find . -iname *something* | xargs -I {} echo {}

In this example the author is piping the input to xargs instead of using the stdin redirect trick we used above (<files.txt xargs ...). That is no problem, we can certainly adjust. Let's combine our approach to getting the input to xargs with the rest of their xargs command:

<files.txt  xargs -I {} echo {}

This did print out my list of files as expected, so it works! Now we can simply replace echo {} with the command we need and we should be on our way

The final solution was:

<input.txt xargs -n1 -I{} mv {} ./input/{}