1

If right now, I only had a bunch of files dumped into a single directory, is there a way that I could move the files into a folder based on a mapping file. For example:

A B
1 File Name Folder Name
2 abc 123
3 def 456
4 ghi 789
5 jkl 123
6 mno 456

is it possible to move file abc and jkl to folder 123 based on this mapping? I'm not familiar with coding; I really just want to know if it's doable.

Thanks in advance!

2
  • I replaced the tags windows and sqlserver with tags that I thought fit the question better. If you want to add your original tags, then you may want to explain how they are relevant.
    – Kusalananda
    Commented May 1 at 22:02
  • 1
    Nicole, do you realise that by asking here you won't get a Windows-centric answer? Commented May 1 at 22:49

3 Answers 3

4

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.

14
  • Hopefully I got it right this time, if someone spots some pitfalls other than those I already mentioned in my answer I'd appreciate them commenting and letting me know.
    – kos
    Commented May 1 at 22:22
  • In bash, wouldn't while IFS=$'\t' read -r src dst; do mv -n -- "$src" "$dst/"; done <pairs be more idiomatic? It doesn't require a subshell, and the quotes protect from globbing as well as word splitting. Commented May 2 at 0:10
  • @steeldriver You're right, that also makes it possible to go around the inability to list paths / filenames containing tabs (removing the -r option from read... Unless I'm missing something again), thank you, I've changed my answer accordingly
    – kos
    Commented May 2 at 0:37
  • 1
    Ture, we just can't tell from the question what the OP wants.
    – Ed Morton
    Commented May 9 at 19:33
  • 1
    @EdMorton Added another version of the command incorporating your suggestion, with a bolded caveat, so that everyone (OP and future visitors, if they desire such behavior) is happy, thanks for the suggestion
    – kos
    Commented May 9 at 23:53
3

If the list is in a CSV file, for example:

$ cat file.csv
"FileName","FolderName"
"A file","/path/to/some directory"
"Another file, with comma and ""quotes""","/some/other
directory with newlines"

You could process it with:

perl -MFile::Copy -MText::CSV=csv -e '
  for (@{csv(in => shift, binary => 1, headers => "auto")}) {
    move $_->{FileName}, "$_->{FolderName}/" or
      die "Cannot move $_->{FileName}: $!\n"
  }' -- file.csv

To also create the target directories if they don't exist:

perl -MFile::Copy -MFile::Path=make_path -MText::CSV=csv -e '
  for (@{csv(in => shift, binary => 1, headers => "auto")}) {
    make_path $_->{FolderName};
    move $_->{FileName}, "$_->{FolderName}/" or
      die "Cannot move $_->{FileName}: $!\n";
  }' -- file.csv
0

Yes, assuming that the file pairs is blank-separated, and none of the file/directory names contain blank characters, you could do:

sed -n "s/^\([^[:blank:]']\+\)[[:blank:]]\+\([^[:blank:]']\+\)\$/mv -- '\1' '\2'/p" | sh
1
  • 1
    Also assumes the file names don't contain apostrophe characters (or otherwise you have an ACE vulnerability). Also assumes GNU sed or compatible for the \+. See \{1,\} for the standard equivalent. Commented May 2 at 7:38

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .