1
function mv1 { mv -n "$1" "targetdir" -v |wc -l ;}
mv1 *.png

It does only move the first .png file it finds, not all of them.

How can I make the command apply to all files that match the wildcards?

6

mv1 *.png first expands the wildcard pattern *.png into the list of matching file names, then passes that list of file names to the function.

Then, inside the function $1 means: take the first argument to the function, split it where it contains whitespace, and replace any of the whitespace-separated parts that contain wildcard characters and match at least one file name by the list of matching file names. Sounds complicated? It is, and this behavior is only occasionally useful and is often problematic. This splitting and matching behavior only occurs if $1 occurs outside of double quotes, so the fix is easy: use double quotes. Always put double quotes around variable substitutions unless you have a good reason not to.

For example, if the current directory contains the two files A* algorithm.png and graph1.png, then mv1 *.png passes A* algorithm.png as the first argument to the function and graph1.png as the second argument. Then $1 is split into A* and algorithm.png. The pattern A* matches A* algorithm.png, and algorithm.png doesn't contain wildcard characters. So the function ends up running mv with the arguments -n, A* algorithm.png, algorithm.png, targetdir and -v. If you correct the function to

function mv1 { mv -n "$1" "targetdir" -v |wc -l ;}

then it will correctly move the first file.

To process all the arguments, tell the shell to process all arguments and not just the first. You can use "$@" to mean the full list of arguments passed to the function.

function mv1 { mv -n "$@" "targetdir" -v |wc -l ;}

This is almost correct, but it still fails if a file name happens to begin with the character -, because mv will treat that argument as an option. Pass -- to mv to tell it “no more options after this point”. This is a very common convention that most commands support.

function mv1 { mv -n -v -- "$@" "targetdir" |wc -l ;}

A remaining problem is that if mv fails, this function returns a success status, because the exit status of commands on the left-hand side of a pipe is ignored. In bash (or ksh), you can use set -o pipefail to make the pipeline fail. Note that setting this option may cause other code running in the same shell to fail, so you should set it locally in the function, which is possible since bash 4.4.

function mv1 {
  local -
  set -o pipefail
  mv -n -v -- "$@" "targetdir" | wc -l
}

In earlier versions, setting pipefail would be fragile, so it would be better to check PIPESTATUS explicitly instead.

function mv1 {
  mv -n -v -- "$@" "targetdir" | wc -l
  ((!${PIPESTATUS[0] && !${PIPESTATUS[1]}}))
}
  • I already made that first correction before I posted the question, but I forgot to correct it in the question as well. – neverMind9 Jun 8 at 22:23
  • “Pass -- to mv to tell it “no more options after this point”.” – Thanks alot. Didn't knew that until now. – neverMind9 Jun 8 at 22:24
  • cite for "$1" splitting at whitespace – Jasen Jun 9 at 7:04
  • You could also use return "${PIPESTATUS[0]}" (immediately after the mv | wc pipeline) instead of messing with pipefail. – Gordon Davisson Jun 9 at 8:20
  • @Jasen It doesn't. Where did you read that? $1, without quotes, splits at whitespace. – Gilles Jun 9 at 8:20
3

$1 is the first argument to the function, here the first file that matches *.png. I guess that "$@" is what you want to use instead of $1.

-2

You would have to use mv1 \*.png.

When interacting with functions, the Linux Terminal does not pass the asterisk directly to the command, but selects the first matching parameter and passes that one to the command.

To allow the asterisk to pass through directly, one needs to escape the asterisk using a backslash.

  • 2
    You understood part of the problem right, but your solution is not good. In particular, it doesn't work with file names containing whitespace. See my answer for a correct solution and explanations. – Gilles Jun 8 at 21:09
  • @Gilles Thank you for the answer. – neverMind9 Jun 8 at 21:34
  • @Gilles …but does that really justify the downvote that was hammered onto my answer? – neverMind9 Jun 8 at 21:35
  • 1
    This answer doesn't answer this part of the question: How can I make the command apply to all files that match the wildcards? I can't see an easy way to expand this answer, because Bash doesn't have a safe way to apply globbing only to the value of a veriable (e.g. if $1 is *.png). – pts Jun 9 at 10:09

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.