I wrote a little code that will copy files given either their name or symlinks to a given directory.

linkcp() {
cp `echo "$(realpath $1)"` "$2"

here is the file list:

$ ls -l
drwxr-xr-x 2 user1 users 4096 apr. 30 01:20 temp
-rw-r--r-- 1 user1 users 50 apr. 30 01:20 file1
-rw-r--r-- 1 user1 users 34 apr. 30 01:20 file2
lrwxrwxrwx 1 user1 users 26 apr. 30 01:20 lnk1 -> file1
lrwxrwxrwx 1 user1 users 26 apr. 30 01:20 lnk2 -> file2

This works if I use:

$ linkcp lnk1 temp
$ ls temp/
$ file1

but not if I use wildcards (I need to move all files beginning with lnk):

$ rm temp/*
$ linkcp lnk* temp
$ ls temp/

If I do:

$ arg=lnk*
$ cp `echo "$(realpath $arg)"` "temp/"
$ ls temp/
$ file1  file2

I don't know why using $1 in the function causes a problem?

  • I am afraid you have overwritten file2 with file1. Furthermore your approach is not going to work with file names which contain whitespace. Apr 29, 2014 at 23:50
  • Right! I understood what happened now. Apr 30, 2014 at 12:30

2 Answers 2


As Hauke pointed out, your problem is that you're expecting 2 arguments but you give your function multiple ones. The link* is expanded by the shell before it's passed to your function so what you're actually running is

linkcp lnk1  lnk2  temp

because lnk* expands to lnk1 lnk2.

So, what you actually want is something like:

linkcp() {
    ## Save the arguments given to the function in the args array

    ## The last element of the array is the target directory

    ## If the target  is a directory
    if [ -d "$target" ];
    ## Iterate through the rest of the arguments given
    ## and copy accordingly
    for((i=0; i<$#-1; i++))
        cp -v "$(realpath "${args[$i]}")" "$target"
    ## If the target does not exist or is not a directory, complain
    echo "$target does not exist or is not a dirtectory" 
  • seems to work perfectly. Apr 30, 2014 at 12:31

Try this to see what really happens:

echo linkcp lnk* temp

linkcp gets more than two arguments but cares about the first two only.

You need something like this:

linkcp() {
  local i num=$# tmparray=()
  for((i=1;i<num;i++)); do
    tmparray[i]="$(realpath "${!i}")"
  if [ "$#" -eq 2 -o "$#" -gt 2 -a -d "${!num}" ]; then
    echo cp "${tmparray[@]}" "${!num}"
    echo "error"

After testing remove the echo.

This takes one argument at a time, determines its real name and calls cp with an argument array instead of a single argument. The array can contain a single argument, too. You may adapt the test if cp shall always copy to a directory only (and not overwrite files or copy to a new file name).

  • There are a few problems in your function. You're not iterating through the arguments, you're iterating through the numbers 1 to $#. You will get errors like 1: no such file or directory if you try to run it. Also, even if you populate tmparray correctly, this approach will fail on file names with spaces since you're passing the whole array at once, instead of quoting individual elements. Also, you could simplify your test by using -ge for greater or equal to.
    – terdon
    Apr 30, 2014 at 11:34
  • @terdon You are right with the first point. I didn't look close enough at my test output. For the rest: I am quite surprised that you seem not to be familiar with the quoting concept of "${array[@]}"... Whether the test can be simplified depends on the exact task. With only two arguments for cp the second one may be a file (maybe this shall not be allowed here, I don't know). Only with more arguments the last one must be a directory. May 1, 2014 at 13:53

You must log in to answer this question.

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