Disclaimer: This answer deals with Bash specifically but much of it applies to the question regarding glob patterns!
The star character (*
) is a wildcard. There are a certain set of characters that it will take the place of and the first character being a dot (.
) isn't one of them. This is a special case just because of how the Unix filesystems work, files that start with a dot are considered "hidden". That means that tools such as cp
, ls
, etc. will not "see" them unless explicitly told to do so.
Examples
First let's create some sample data.
$ mkdir .dotdir{1,2} regdir{1,2}
$ touch .dotfile{1,2} regfile{1..3}
So now we have the following:
$ tree -a
.
|-- .dotdir1
|-- .dotdir2
|-- .dotfile1
|-- .dotfile2
|-- regdir1
|-- regdir2
|-- regfile1
|-- regfile2
`-- regfile3
Now let's play some games. You can use the command echo
to list out what a particular wildcard (*
) would be for a given command like so:
$ echo *
regdir1 regdir2 regfile1 regfile2 regfile3
$ echo reg*
regdir1 regdir2 regfile1 regfile2 regfile3
$ echo .*
. .. .dotdir1 .dotdir2 .dotfile1 .dotfile2
$ echo .* *
. .. .dotdir1 .dotdir2 .dotfile1 .dotfile2 regdir1 regdir2 regfile1 regfile2 regfile3
$ echo .dotdir*
.dotdir1 .dotdir2
Changing the behavior?
You can use the command shopt -s dotglob
to change the behavior of the *
so that in addition to files like regfile1
it will also match .dotfile1
.
excerpt from the bash
man page
dotglob If set, bash includes filenames beginning with a `.' in the results
of pathname expansion.
Example:
$ shopt -s dotglob
$ echo *
.dotdir1 .dotdir2 .dotfile1 .dotfile2 regdir1 regdir2 regfile1 regfile2 regfile3
You can revert this behavior with this command:
$ shopt -u dotglob
$ echo *
regdir1 regdir2 regfile1 regfile2 regfile3
Your situation?
For you you're telling cp
that you want to copy all the files that match the pattern *
, and there aren't any files.
$ cp foo/.* .
Or you can do this if you want everything in the foo
folder:
$ cp foo .
Or you can be explicit:
$ cp foot/.* foo/* .
A more compact form using brace expansion in bash
:
$ cp foo/{.,}* .
At any time you can use the echo
trick to see what your proposed file patterns (that's the fancy term for what the star is a part of).
$ echo {.,}*
. .. .dotdir1 .dotdir2 .dotfile1 .dotfile2 abc regdir1 regdir2 regfile1 regfile2 regfile3
Incidentally if you're going to copy a directory of files + other directories, you typically want to do this recursively, that's the -R
switch to cp
:
$ cp -R foo/. .
I would store the list of files in an array, so that you don't have to read the file system twice, increasing performance and reducing potential race conditions. Then use another variable as the index.
files=(*.jpg)
total=${#files[@]}
i=0
for f in "${files[@]}"; do
i=$(( i + 1 ))
echo index $i
echo total $total
echo "- Processing file: $f"
done
Explanation
files=(*.jpg)
: store the glob into the array $files
total=${#files[@]}
: read the total into $total
i=0
: initialise $i
to 0.
i=$(( i + 1 ))
: add 1 to $i
each loop
This presumes that the "first" loop is 1. Depending on your opinion, you might want to start at 0 instead.
Best Answer
This requires a careful use of quotes:
But that may fail on filenames with spaces (or newlines). Better to use this:
The glob gets correctly expanded and placed in the positional parameters with:
Then, each parameter is placed as an argument to
stat
in:And the output of
stat
goes (as one output) toawk
.