Unix command to move multiple files at once to a target directory using parameterized filename?

85 Views Asked by At

I am trying to move specific file names all at once from one directory to other using single command line. I have captured the file names (not with full path) in a variable as shown below, it may have 100 to 1000 files names.

var="M1.txt,A2.dat,T300.log"

I tried following command its working mv /home/rrajendr/test1/{M1.txt,A2.dat,T300.log} /home/rrajendr/test2/

Same is not working if it's parameterized mv /home/rrajendr/test1/{"$var"} /home/rrajendr/test2/

Error mv: cannot stat ‘/home/rrajendr/test1/{M1.txt,A2.dat,T300.log}: No such file or directory

Its looking for a filename as exactly like this "{M1.txt,A2.dat,T300.log}" as is, its not interpreting as individual file name. Something is missing here, please let me know

I tried following option but didn't worked

mv /home/rrajendr/test1/echo $var -t /home/rrajendr/test2/

mv /home/rrajendr/test1/{$var}

mv /home/rrajendr/test1/{echo $var}

Reason I am looking this option

  1. My sourcepath is fixed and I dont want to define back to back with full qualifier for each file name because I have 100 of files.
  2. I don't want to switch to the file directory (cd /home/rrajendr/test1/) back and forth, as I am doing other operation like aws cli operation & snow cli commands.
  3. I have already stored 100 of file names in a variable as like this string var="M1.txt,A2.dat,T300.log"
  4. Don't want to do this other program like python etc, not looping in shell with for or while also not with xargs
  5. To initiate a multiple files moves using single command line
4

There are 4 best solutions below

1
Jens On BEST ANSWER

If you are creating the file list and know there are no funny characters in the list, I'd use eval:

cmd="mv /home/rrajendr/test1/{$var} /home/rrajendr/test2"
eval $cmd

or even

eval "mv /home/rrajendr/test1/{$var} /home/rrajendr/test2"

Note that brace expansion is not (yet) a POSIX shell feature as of 2023. This will only work if your sh is bash or zsh or any other shell supporting brace expansion.

I'd say that the proper way to do this, with much less opportunities to break, is to loop or use xargs. Why do you want to avoid that? Moving files is never a bottle neck.

1
kvantour On

Brace expansion is done before variable substitution within GNU Bash, Z Shell, and Korn Shell. This is why the attempted command fails as the brace only sees the string $var and not the value assigned to it.

A simple workaround would be to use eval, but be aware that eval is evil (see below).

Generally, when creating a variable that keeps track of files, it is recommended to use arrays instead of variables. In the case of the OP, this would then result in any of the following solutions:

file_list=( "M1.txt" "A2.dat" "T300.log" )
cd /path/to/old/dir; mv -t /path/to/new/dir -- "${file_list[@]}"
mv -t /path/to/new/dir -- "${file_list[@]/#//path/to/old/dir/}"
for _f in "${file_list[@]}"; do mv -- "/path/to/old/dir/${_f}" /path/to/new/dir; done
...

Eval is evil:

3
jhnc On

For a variable var containing a comma-delimited list of filenames, you can do:

( cd /home/rrajendr/test1 && { IFS=,; mv -i -- $var /home/rrajendr/test2; } )

POSIX shells perform word-splitting during variable expansion. Normally, it is good practice to quote variables to avoid word-splitting but here we can make use of this by setting IFS and not quoting so that the comma-delimited string stored in var is split into the multiple filenames upon expansion.

By changing into the source directory, we don't need to include it in the filenames. We use && to avoid copying the wrong files if the cd fails.

As noted by Jens, setting IFS only takes effect on the following command, so we wrap them both in { ... } so they are treated as a single element in the && list.

We pass -- to mv in case var contains filenames that could be misinterpreted as options.

We wrap the whole commandline in ( ... ) so that we don't have to cd back to the original directory explicitly.

The question uses absolute paths. If it hadn't, it would be necessary to compute the absolute path for the destination before doing the cd. For example, if the command was run in /home and used relative paths, we could do:

( cd rrajendr/test1 && { IFS=,; mv -i -- $var "$(realpath rrajendr/test2)"; } )
0
Rajbharath On

Thanks to all who spent their time on this question

@Jens, @jhnc & @kvantour

I have tried your suggestions, it worked perfectly and satisfied all my points

cmd="mv /home/rrajendr/test1/{$var} /home/rrajendr/test2" eval $cmd

I peformance tested this with 100 to 150 actual files movement using variable, it worked well

My actual file name length will be like "GTRDW_Download_Status-CL-US-C2-614919452-03-21-2023.11_59-en-0042.zip"

Just a note to future readers

  1. This satisfied my need and worked well with moving limited files after some other S3 & Snowflake operations.
  2. If you planning to move huge number of files better suggestion its put in list/array get this execute in loop or xargs to avoid any buffer/memory issues