Using xargs to move multiple files at once (on MacOS)
xargsBackground #
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/{}