0

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
Rajbharath
  • 13
  • 4
  • 1
    You are tagging this as POSIX shell question, but AFIK, POSIX does not define brace expansion. Hence it is not clear, what shell you are using. – user1934428 Mar 22 '23 at 07:54
  • 1
    `I have captured the file names (not with full path) in a variable` But why did you use comma? Where from are you "capturing"? – KamilCuk Mar 22 '23 at 11:31
  • Thanks for all your response, I am trying to do this in Unix shell scripting (.sh). Please ignore if the tag was incorrect. – Rajbharath Mar 22 '23 at 11:31

4 Answers4

0

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.

Jens
  • 69,818
  • 15
  • 125
  • 179
  • Thanks @Jens appreciate you suggestions and explanations, I will try following options **cmd="mv /home/rrajendr/test1/{$var} /home/rrajendr/test2" eval $cmd** – Rajbharath Mar 22 '23 at 11:49
0

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:

kvantour
  • 25,269
  • 4
  • 47
  • 72
  • Thanks @kvantour appreciate you suggestions and explanations, I will try following option **mv -t /path/to/new/dir -- "${file_list[@]/#//path/to/old/dir/}"** – Rajbharath Mar 22 '23 at 11:47
0

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)"; } )
jhnc
  • 11,310
  • 1
  • 9
  • 26
  • Thanks for all your response, I am trying to do this in Unix shell scripting (.sh). Let me know if its works in .sh, If yes then is it possible to do this without switching to the directory "cd rrajendr/test1", It's fine to define the path but don't want to switch it because I do other operations which also needs switching. – Rajbharath Mar 22 '23 at 11:39
  • The command should work with a [POSIX-conformant shell](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sh.html). The point of using `( ... )` is that after the command completes, the directory is unchanged. – jhnc Mar 22 '23 at 12:30
  • eg. try: `cd /home; pwd; (cd /tmp; pwd); pwd` – jhnc Mar 22 '23 at 12:40
0

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
Rajbharath
  • 13
  • 4