3

According to man cp, using cp -P * foo/ means that no symbolic links will be followed:

-P: No symbolic links are followed. This is the default if the -R option is specified.

But how to copy files in the same way, that is, without following symbolic links, using Zsh glob qualifiers and without using -P? Do I need to use cp *(.) foo/ or do I need to use cp *(-.) foo/ instead?

From What are the practical differences between Bash and Zsh? on Ask Different:

  • foo*(.): only regular files matching foo* and symbolic links to regular files, not directories and other special files.
  • foo*(-.): only regular files matching foo*, not symbolic links and other special files.

It seems the answer is foo*(-.), but I'm not sure and I really want to figure out such things.

zsh 5.9 (x86_64-apple-darwin23.0)

6
  • 1
    -P is the default, and it copies the symlink itself (without following it), where -L would follow the link and copy the linked-to file. Do you want to copy the links as-is, or just ignore them completely? And also, you're not using -R, right? So the question is just about filenames listed on the cp command linei? It might help if you could show a small example of an example source directory and the expected result. (It would need to be representative enough to not give the wrong ideas.)
    – ilkkachu
    Commented May 19 at 7:07
  • @ilkkachu "Do you want to copy the links as-is, or just ignore them completely?" - Well, currently I simply try to figure out how to replicate "old" behavior using "new" tools. But it seems kos means globs are not replacement for -P
    – jsx97
    Commented May 19 at 7:16
  • @jsx97, yes, but what exactly is the "old behaviour" you want?
    – ilkkachu
    Commented May 19 at 7:23
  • And, I was sloppy, -P is the default with -R (and doesn't even take effect without -R with the cp on Mac), so just cp link.txt foo.txt will follow the link and create a new copy of the file itself, and that's what you'll get if the glob in something like cp * somewhere expands to the name of a link.
    – ilkkachu
    Commented May 19 at 7:27
  • 1
    Alright after the double -P default / not default fiasco I'm just gonna delete my comment and retire, but I'll leave this here since it's what I wanted to add to this conversation: (:A) is probably what you're looking for, if you want to expand a symlink regardless of cp's behavior, as it will expand the symlink before cp can even process paths.
    – kos
    Commented May 19 at 7:32

1 Answer 1

5
  • cp -P -- * foo/ (I've added the missing -- without which files starting with - could be treated as options by cp) would copy all files into foo/. For files of type symlink, that would copy the symlink (without changing the symlink target which means the copied link will likely become invalid if it's not to an absolute path or a file in the same directory).

    It will fail for files of type directory which cp doesn't copy without the -R option. Even with -R it would fail for foo itself as cp will refuse to copy it into itself.

  • cp -- *(.) foo/ will copy only files of type regular (as selected by the . glob qualifier), to the exclusion of any other type of file including symlink, directory, fifo, device... So -P and -R options are irrelevant here.

  • cp -- *(-.) foo/ also copies the files of type regular but this time, the type of file is checked after symlink resolution (thanks to the - qualifier), so it will also copy symlinks which can be determined to eventually resolve to files of type regular. For those, without -P, the copy in foo/ will be a regular file with same content as the one the symlink resolves to.

In any case, globs and their glob qualifiers don't copy files, they are just a shell tool to help you select files to pass to commands. To copy files, you typically use cp¹.

Here, if you want to copy all non-hidden files into foo as-is (copying symlinks as symlinks even if that means making them broken links and as much metadata as possible) excluding those of type directory, you'd do:

cp -Pp -- *(^/) foo/

Using the / qualifier (selects files of type directory) negated with ^.

To copy all files, including hidden ones and directories (including their full contents recursively):

set -o extendedglob
cp -RPp -- ^foo(D) foo/

(the D qualifier, like the dotglob option prevents the skipping of hidden files aka dotfiles).

Where we explicitly exclude foo from the list of files to copy to avoid cp's error. Note that with some cp implementations, -P is the default when -R is passed, but that's not the case of all cp implementations and it's left unspecified by POSIX.

I don't know about macos' own cp implementation, but with the cp implementations of GNU, busybox and toybox, cp -RPp . foo/ also works except for the error about copying foo into itself.

With GNU cp, that still copies the foo directory itself, but not its contents. With the cp implementation from ast-open, that does enter an infinite recursion copying foo and all its contents into foo.


¹ Though you could also use pax, tar, cpio, rsync or even some shell builtins such as zsh's $mapfile special associative array or some of its builtins such as read/print/sysread/syswrite with redirections.

4
  • Many thanks, Stéphane. Could you explain, what is the difference between cp -Pp -- *(^/) foo/ and cp -Pp -- * foo/? It seems both copy regular files as well as symbolic links, but I suppose there is a difference if we try to copy, e.g., a device file or a FIFO?
    – jsx97
    Commented May 19 at 14:50
  • See edit. That's all in the manual. Commented May 19 at 18:22
  • I read the docs, just want to be sure. The first command, with *(^/), copies any types of files except directories, because we excluded directories by using (^/). The second command, with *, doesn't copy directories either, because there is no -R flag, but, the same as the first command, copies any other types of files. So there is no difference between them, correct?
    – jsx97
    Commented May 19 at 18:38
  • 1
    @jsx97 With cp -Pp -- * foo/, I'd expect cp to report an error for each files of type directory that are found in the expansion of *. Excluding them from that expansion by adding (^/) avoids those errors. Commented May 19 at 18:59

You must log in to answer this question.

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