Just one file echoed from an array of filesMm 9Aao P H wllQqKk Lx YsRr ZzSs Qqt UmO u12Rr Ff Ee
I try to copy 2 or more files from one directory to another with cp using an array.
I executed:
files=(
LocalSettings.php
robots.txt
.htaccess
${domain}.png
googlec69e044fede13fdc.htm
)
filenames indented with tabulations;
I aim to execute afterwards:
cp -a "source_path/${files[@]}" "/destanation_path"
my problem is that while testing the variable itself,
echo $files returned only the first filename LocalSettings.php and not the full list of files.
How would you explain this?
Related: cp in multi line fashion;
4 Answers
It's Bash feature described in man bash:
Referencing an array variable without a subscript is equivalent to referencing the array with a subscript of 0.
If you want to print all members of files array:
echo "${files[@]}"
Also described in man bash:
${name[@]} expands each element of name to a separate word.
$files and ${files[0]} is equivalent when files is a list such as the one you have in your question.
Note that "source_path/${files[@]}" only puts source_path/ before the first element of the list.
To modify the list in such a way that each element is prefixed by some path, you can do
files=( ... your list of files ... )
for element in "${files[@]}"; do
files=( "${files[@]:1}" "source_path/$element" )
done
cp "${files[@]}" destanation_path
or, you could just cd to source_path before doing the cp, or add the path to the actual names at the same time as you assign the values in the list from the start.
As others have pointed out, $files only expands to the first element of the array, and "source_path/${files[@]}" only attaches "source_path/" to the first element. But there's a relatively simple way to get all elements, with a path prepended to each:
cp -a "${files[@]/#/source_path/}" "/destanation_path"
This combines the all-elements expansion ([@]) with a substitution. /# means "replace at beginning of string", then the empty string to replace, then / to delimit the replacement, then "source_path/" as the thing to replace (/add). This attaches the source path to each element, and doesn't get confused by funny characters in the elements like some versions do.
Note that in the above example, "source_path/" has a slash at the end but not the beginning; the "/" right before it is a delimiter. If it started with a slash and contained more slashes, like "/source/path/", it'd still work:
cp -a "${files[@]/#//source/path/}" "/destanation_path"
You can also try the following snippet:
IFS=$'\\n'
cp -a $( printf "source_path/%s\\n" "${files[@]}" ) /destination_path/
It should also work with filenames with spaces.
-
It works with file names containing spaces, but not with file names containing newlines or wildcards. It's also completely pointless, since
cp -a "${files[@]}" /destination_path/works strictly better. – Gilles 7 hours ago
cdto change to the source directory before copying. If I didn't do that, I would have to specify the full path to the files in the list, e.g.source_path/LocalSottings.php source_path/robots.txtetc. Also$fileswould always be the same as${files[0]}. – Kusalananda♦ 10 hours ago