I read How can I copy a file and create the target directories at the same time?
But the solution there only works if you want to copy one file.
How would I copy multiple files into a directory, that doesn't exist yet and auto-create it with one command?
for example:
cd /tmp/
mkdir a/
cd a/
touch x1 x2
cp --magic * ../b/c/d/
so in the end the two files x1
and x2
are ordered like this:
/tmp/b/c/d/x1
/tmp/b/c/d/x2
-
2Most of the solutions in unix.stackexchange.com/questions/41770/… work for any number of files. Don't stop reading at the accepted answers.Gilles 'SO- stop being evil'– Gilles 'SO- stop being evil'2014年10月08日 23:10:34 +00:00Commented Oct 8, 2014 at 23:10
2 Answers 2
Use:
$ copy() { mkdir -p -- "${@: -1}" && cp -- "$@" ; }
$ copy * ../b/c/d/
Note: it works with bash, ksh93 and zsh.
"${@: -1}"
corresponds to the ${parameter:offset}
form: all parameters starting at the offset. The offset -1 corresponds to the last parameter, since negative offsets count from the end. The space is necessary, otherwise it would be the ${parameter:-word}
form. See the bash manual for more information.
"$@"
expands to the list of parameters, which is what is wanted for cp
.
Note 2: As suggested by mikeserv, you can use
eval 'mkdir -p -- "${'"$#"'}"'
which is more portable (POSIX), instead of
mkdir -p -- "${@: -1}"
Concerning the performance, the eval
solution is much faster with bash 4.3.30, but the other solution is faster with ksh 93u+ and zsh 5.0.6 (ksh93 and zsh being much faster than bash in both cases); and dash 5.7 with the eval
solution (the other one is not supported) is a bit faster than ksh93 with the "${@: -1}"
solution. So, depending on your case (script, interactive use, support for specific shell features, etc.), make your choice... Note however that in the context of cp
(which will take most of the time), these performance differences won't be noticeable.
The script I used for the test:
i=50000
while [ $i -ne 0 ];
do
# : "${@: -1}"
eval ': "${'"$#"'}"'
i=$((i-1))
done
(comment out the eval
line and uncomment the previous line for the other solution), called with:
time sh ./tst `seq 1000`
time bash ./tst `seq 1000`
time ksh93 ./tst `seq 1000`
time zsh ./tst `seq 1000`
-
It could be more variable like
cp
if you would like to use this command as a complete ceplacement forcp
: for example, if you copy only one file and the target does not end with a slash, then it should copy and rename the file to the target instead of creating a folder with the target namerubo77– rubo772014年10月08日 23:54:29 +00:00Commented Oct 8, 2014 at 23:54 -
You can do roughly the same thing POSIXLY like
eval 'mkdir -p -- "${'"$#"'}"' && cp -- "$@"
. Doing it that way also saves the shell from having to negatively index - and likely therefore iterate entirely through - the positional array which can be be computationally costly.mikeserv– mikeserv2014年10月09日 05:47:16 +00:00Commented Oct 9, 2014 at 5:47 -
Thinking twice, it is probably worth testing for at least one arg though, like -
..."${'"$(($#?$#:1))"'}"...
which would save accidental creation of directories named0ドル
- which your solution will not do.mikeserv– mikeserv2014年10月09日 05:54:24 +00:00Commented Oct 9, 2014 at 5:54 -
@mikeserv If one wants to detect errors on the number of parameters, it's probably better to do it the usual way (
if [ $# -lt 2 ]; then ...
) and output an error message and return with a non-zero exit status. I think that anyone knows how to do this. There are various other ways to modify the command, e.g. to support the usual options, but this is getting more complex.vinc17– vinc172014年10月09日 09:19:18 +00:00Commented Oct 9, 2014 at 9:19 -
That does a non-zero exit status in the event the function is called without arguments - its return comes from
mkdir -p -- "1ドル"
. I dont know why you'd put a shell parser reserved word-type complex command likeif
on a single line like that. Another way might befn() { ${2+:} return 1;...}
or as a simpler means of doing the same the math expression does above:...${2+"${'"$#"'}"}...
mikeserv– mikeserv2014年10月09日 09:29:46 +00:00Commented Oct 9, 2014 at 9:29
I had this same problem and used tar
to solve it! Like so:
tmpfile=/tmp/myfile.tar
files="/some/folder/file1.txt /some/other/folder/file2.txt"
targetfolder=/home/you/somefolder
tar --file="$tmpfile" "$files"
tar --extract --file="$tmpfile" --directory="$targetfolder"
In this case, tar
will automatically create all (sub)folders for you! Best,
Nabi