0

I have some files named like: starting with "title." and ending with ".txt", and between them, there may be something else or maybe not. For example: title.txt, title.abc.txt, title.123.txt.

Now I want to copy all of them to another folder. How can I use one cp command to copy all the files?

Using regular expression, this can be done as:

ls | egrep 'title(\..*)*\.txt' | xargs -I{} cp {} destination_folder/

But it's too complicated: you need help from egrep and xargs. Is there an easy way using shell's built-in function glob/wildcards to do the magic? Something like:

cp title[magic]txt destination_folder/
3

There's no equivalent in the standard shell wildcards, but some shells have some extension for that:

zsh

in zsh, that's the # operator, enabled with the extendedglob option:

set -o extendedglob # best in ~/.zshrc
cp -- *title(.*)#.txt* dest/

You can use EREs or PCREs in globs using the e glob qualifier:

cp -- *(e['[[ REPLY =~ "title(\..*)*\.txt" ]]']) dest/

(issue set -o rematchpcre for that to use PCREs instead of EREs, or use -pcre-match instead of =~ after zmodload zsh/pcre)

ksh

In ksh (all versions), x* is *(x):

cp -- *title*(.*).txt* dest/

bash

bash supports the ksh operators with the extglob option:

shopt -s extglob
cp -- *title*(.*).txt* dest/

ksh93

ksh93 can use regexps in its globs:

cp -- *~(E:title(\..*)*\.txt)* dest/

(E for Extended regexps here).

It can also convert EREs to glob patterns with:

$ printf '%P\n' 'title(\..*)*\.txt'
*title*(\.*)\.txt*

(those backslashes are superfluous here though)

2
  • Isn't that original just title.*.txt title.txt? (Or some variant with nullglob and the one outlier written as a glob to avoid the possible "File not found" error.)
    – ilkkachu
    Jul 3 '20 at 17:27
  • In that case yes though that should be *title.*.txt* *title.txt* to be functionally equivalent to his egrep approach. I suspect they meant something like ^title(\.[^.]+)*\.txt$. In any case, this answer focuses on the what's the shell glob equivalent of regexp * question. Jul 3 '20 at 18:39
0

The only way to match the specific same regex in basic globbing is to do:

cp title[.]txt title.*.txt destination_folder/

Assuming that the files exist (or set nullglob and then no file at all will generate a cp error).

Or:

$ ksh -c '
      set -- ~(N)title[.]txt ~(N)title.*.txt
      [ $# -ge 1 ] && cp "$@" dir/
  '

Or:

$ bash -O nullglob -c '
    set -- title[.]txt title.*.txt
    [ $# -ge 1 ] && cp "$@" dir/
  '

Or similar in other shells.


Why

Technically, there isn't any possible equivalent of a regex's * in a glob.

Mainly because a regex single * has no meaning. It gets meaning when there is something before it, and it then means repeat the previous token (the something) 0 or more times.

What has an exact equivalent is: a regex of .* is exactly equivalent to a glob of *.

The equivalent of a regex like title(\..*)*\.txt is near to title.*.txt.

start and end

I assume the exact regex you should have written for grep is:

^title(\..*)*\.txt$

Which will comply with your statements of starting with "title." and ending with ".txt". Starting should be signaled with ^ and ending should be signaled with $ in a regex.

The start and end are included by default in a glob.

dot

A dot, being an special character in a regex, should be escaped: \..

A dot is not special in a glob and needs no escaping.

optional character

But there is no way to express an optional character in a glob (basic).

A (c|d)?, which in a regex means: "an optional c or d", or, in simple words: a character c or d zero or one time, has no equivalent in basic glob. To get that, you need to use extended globbing. The syntax for an optional string is:

?(c|d)        # glob

So:

?(.*)         # zero or once `?( )` a dot followed by anything (`*`).

Means: optionally match a dot followed by anything (even empty).

And the whole glob would be:

title?(.*).txt            # only in extended globbing.

The way to set the extended globbing vary per shell (is not portable):

  • ksh : on by default, no need to set anything.
  • bash : shopt -s extglob
  • zsh : set -o extendedglob

portable (almost)

The only way to match the specific same regex in basic globbing is to do:

echo title[.]txt title.*.txt 

nullglob

But all the above matching is assuming some match will result of expanding the glob. If it may happen that a glob will not find a match (like title[.]txt not finding a title.txt file) then you should also set the nullglob shell option to remove the glob (as opposed of leaving it in the command line).

But that is also not portable:

  • ksh : Has no nullglob option. Only works using echo ~(N)title?(.*).txt
  • bash : shopt -s nullglob.
  • zsh : set -o NULL_GLOB or set -G

Also, if nullglob is set and no pattern is matched, cp will generate an error, as cp will "see" this command line:

cp  dir/

Where no source is given.

failglob

A similar problem is generated by failglob. If any of several patterns are not matched, the whole command fails.

cp title[.]txt title?(.*).txt dir/

will fail if any of the two patterns fail.

Final

The only way to make the cp work even with no match at all and no getting an error from cp, is:

$ bash -O nullglob -c '
    set -- title[.]txt title.*.txt
    [ $# -ge 1 ] && cp "$@" dir/
  '

$ ksh -c '
      set -- ~(N)title[.]txt ~(N)title.*.txt
      [ $# -ge 1 ] && cp "$@" dir/
  '

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.