You could export the file, exclusive of the header, to a tab-delimited CSV (let's call it "pairs" - the CSV shall not be exported double-quoting or otherwise delimiting text fields - see the caveats below) and then run this in Bash:
while IFS=$'\t' read src dst; do mv -n -- "$src" "$dst/"; done <pairs
(thanks to @steeldriver for improving on my original answer)
If you also want it to create the destination directory if it doesn't exist (warning: this will allow typos in the destination directory to go through unnoticed):
while IFS=$'\t' read src dst; do mkdir -p "$dst" && mv -n -- "$src" "$dst/"; done <pairs
(thanks to @EdMorton for suggesting this option)
while IFS=$'\t' read src dst; do [...]; done <pairs
: will set the shell's internal field separator to a tab character while executing read
, which will read from "pairs" one line at the time, splitting the line's contents on tab characters and into two variables (src
and dest
) at each iteration
mv -n -- "$src" "$dst/"
: will run mv
on src
and dest
, avoiding overwriting existing files
$ cat pairs
baz foo/bar
$ tree -ap .
[drwxrwxr-x] .
├── [-rw-rw-r--] baz
├── [drwxrwxr-x] foo
│ └── [drwxrwxr-x] bar
└── [-rw-rw-r--] pairs
3 directories, 2 files
$ while IFS=$'\t' read src dst; do mv -n -- "$src" "$dst/"; done <pairs
$ tree -ap .
[drwxrwxr-x] .
├── [drwxrwxr-x] foo
│ └── [drwxrwxr-x] bar
│ └── [-rw-rw-r--] baz
└── [-rw-rw-r--] pairs
3 directories, 2 files
There are advantages to using this method, such as the lack of need to escape characters normally included in IFS
(besides tabs - you should escape tabs) and the lack of need to escape shell metacharacters when creating the file;
But there are also limitations, such as: source and destination paths / filenames won't undergo tilde expansion, so you won't be able to refer to /home/<your_username>
using ~
.
Also, no support for source and destination paths / filenames containing newlines.