Given the input
a
b
c
I am trying to execute command foo
with each input as a separate argument (e.g. 'n' inputs means a single call to foo
with 'n' arguments)...
foo a b c
I am not trying to execute foo
once per argument (e.g. 'n' inputs means 'n' calls to foo
each with a single argument iteration)
# Not this!
foo a
foo b
foo c
Can this be done outside of string concatenation with eval
?
I'm trying to find the recommended way to pass the multi-line output of one command as individual arguments to a second command. To be clear, I am not trying to execute that second command, once per argument. I'm trying to pass all the arguments to a single command instance.
This is easier demonstrated than explained.
Consider this shell command with a hard-coded list of three swift source files (note some have spaces and are in subfolders)...
xcrun -sdk macosx swiftc -o "$OUTPUT_FILE" "main.swift" "car.swift" "Models/Road Bike.swift"
It's pretty straightforward and executes as expected.
But... I'm trying to automate things so I want that list of source files to be the result of a recursive find
operation.
Here's the find
command I started with...
find * -type f -name "*.swift"
Executing that yields the following results...
Models/Road Bike.swift
car.swift
main.swift
As such, for my first attempt, I tried this...
SOURCE_FILES=$(find * -type f -name "*.swift" | tr '\n' ' ')
xcrun -sdk macosx swiftc -o "$OUTPUT_FILE" $SOURCE_FILES
...but it didn't work. I (incorrectly) thought it was because the filenames weren't properly quoted.
Because of the above I changed it to this to wrap each filename accordingly...
SOURCE_FILES=$(find * -type f -name "*.swift" | while read fn; do echo \"$fn\"; done | tr '\n' ' ')
xcrun -sdk macosx swiftc -o "$OUTPUT_FILE" $SOURCE_FILES
This did properly add the quotes to the filenames, but that's when I realized my earlier error... it's not treating it as separate arguments, one per filename, it's treating it as a single argument with one giant filename with embedded quotes (and last time it was one giant filename without embedded quotes).
Ok, that's when I realized I could go brute-force and just build the exact command I wanted as a literal string, then execute it using eval
. Armed with that knowledge, I finally went with this...
SOURCE_FILES=$(find * -type f -name "*.swift" | while read file; do echo \"$file\"; done | tr '\n' ' ')
cmd='xcrun -sdk macosx swiftc -o "$OUTPUT_FILE" '$SOURCE_FILES
eval $cmd
...and sure enough it worked. BUT... I can't help but feel this is workaround after workaround after workaround.
Is there a simpler way to pass the output from the find
(or ls
, etc.) command as individual input arguments to the compile (or any other) command? How else can this be solved? What's the recommended/preferred way to do this?
Note: I'm using zsh but if it's also bash-compatible, that would be good too.
You're trying too hard.
find * -type f -name "*.swift" -print0 | xargs -0 xcrun -sdk macosx swiftc -o "$OUTPUT_FILE"