2

I'm trying to write a basic find command for a assignment (without using find). Right now I have an array of files I want to exec something on. The syntax would look like this:

-exec /bin/mv {} ~/.TRASH

And I have an array called current that holds all of the files. My array only holds /bin/mv, {}, and ~/.TRASH (since I shift the -exec out) and are in an array called arguments.

I need it so that every file gets passed into {} and exec is called on it.

I'm thinking I should use sed to replace the contents of {} like this (within a for loop):

for i in "${current[@]}"; do
    sed "s@$i@{}" 
    #exec stuff?
done

How do I exec the other arguments though?

4
  • @anubhava Sorry, I meant I'm writing a basic find command, but I am not allowed to use find. Commented Feb 27, 2015 at 11:23
  • @anubhava I need to replace the string {} with the file i'm passing in from current, then I need to exec mv that file to trash. So I'm replacing each instance of {} with that file, then executing a command on that file. I don't know, if thats how it's supposed to work though or if there is an easier way. Commented Feb 27, 2015 at 11:27
  • Sorry you won't get answer here without providing total clarity. Do you have literal {} in your input files? Commented Feb 27, 2015 at 11:37
  • @anubhava Sorry about that :( I do not. Most of the files are just regular files, ie. test.txt. So if I have test1.txt, test2.txt in my current array, and the user passes in -exec /bin/mv {} ~/.TRASH, then it will look like this: for i in current[0] ... exec mv test1.txt ~/.TRASH ... for i in current[1] ... exec mv test2.txt ~/.TRASH It goes through every file and executes the mv command Commented Feb 27, 2015 at 11:40

2 Answers 2

1

You can something like this:

cmd='-exec /bin/mv {} ~/.TRASH'    
current=(test1.txt test2.txt)

for f in "${current[@]}"; do 
   eval $(sed "s/{}/$f/;s/-exec //" <<< "$cmd")
done

Be very careful with eval command though as it can do nasty things if input comes from untrusted sources.


Here is an attempt to avoid eval (thanks to @gniourf_gniourf for his comments):

current=( test1.txt test2.txt )
arguments=( "/bin/mv" "{}" ~/.TRASH )

for f in "${current[@]}"; do
   "${arguments[@]/\{\}/$f}"
done
Sign up to request clarification or add additional context in comments.

8 Comments

Unfortunately this just outputs /bin/mv a ton of times :(
I expected it to move the files to trash. Also if you do /bin/ls it will just output /bin/ls/ many times. Is there a reason for this?
Actually doing exec $cmd after resolve the issue. Thank you for getting me on the right track :)
This is really dangerous in many ways! you really should not call eval at all! filenames should be handle like user input: potentially dangerously destructive. Never call eval on code you don't control.
Yes I totally agree with you and wrote about it in the answer. Problem is that OP wrote: user passes in -exec /bin/mv {} ~/.TRASH Now we have -exec /bin/mv {} ~/.TRASH in a string and then need to run the command. OP's own attempt of running via exec will pose same amount of risk.
|
1

Your are lucky that your design is not too bad, that your arguments are in an array.

But you certainly don't want to use eval.

So, if I understand correctly, you have an array of files:

current=( [0]='/path/to/file'1 [1]='/path/to/file2' ... )

and an array of arguments:

arguments=( [0]='/bin/mv' [1]='{}' [2]='/home/alex/.TRASH' )

Note that you don't have the tilde here, since Bash already expanded it.

To perform what you want:

for i in "${current[@]}"; do
    ( "${arguments[@]//'{}'/"$i"}" )
done

Observe the quotes.

This will replace all the occurrences of {} in the fields of arguments by the expansion of $i, i.e., by the filename1, and execute this expansion. Note that each field of the array will be expanded to one argument (thanks to the quotes), so that all this is really safe regarding spaces, glob characters, etc. This is really the safest and most correct way to proceed. Every solution using eval is potentially dangerous and broken (unless some special quotings is used, e.g., with printf '%q', but this would make the method uselessly awkward). By the way, using sed is also broken in at least two ways.

Note that I enclosed the expansion in a subshell, so that it's impossible for the user to interfere with your script. Without this, and depending on how your full script is written, it's very easy to make your script break by (maliciously) changing some variables stuff or cd-ing somewhere else. Running your argument in a subshell, or in a separate process (e.g., separate instance of bash or sh—but this would add extra overhead) is really mandatory for obvious security reasons!

Note that with your script, user has a direct access to all the Bash builtins (this is a huge pro), compared to some more standard find versions2!


1 Note that POSIX clearly specifies that this behavior is implementation-defined:

If a utility_name or argument string contains the two characters "{}", but not just the two characters "{}", it is implementation-defined whether find replaces those two characters or uses the string without change.

In our case, we chose to replace all occurrences of {} with the filename. This is the same behavior as, e.g., GNU find. From man find:

The string {} is replaced by the current file name being processed everywhere it occurs in the arguments to the command, not just in arguments where it is alone, as in some versions of find.


2 POSIX also specifies that calling builtins is not defined:

If the utility_name names any of the special built-in utilities (see Special Built-In Utilities), the results are undefined.

In your case, it's well defined!


I think that trying to implement (in pure Bash) a find command is a wonderful exercise that should teach you a lot… especially if you get relevant feedback. I'd be happy to review your code!

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.