Home > Software engineering >  How can you pass a multi-line output from one command as multiple arguments to another command?
How can you pass a multi-line output from one command as multiple arguments to another command?

Time:07-07

Update - xargs seems to be the way to go, but...

Per an answer below, it seems this is precisely what xargs is used for. As such, I changed the code to this...

SOURCE_FILES=$(find * -type f -name "*.swift")
echo $SOURCE_FILES | xargs -0 xcrun -sdk macosx swiftc -o "$OUTPUT_FILE"

While this is clearly passing in each output from find as an argument as expected (as is evident by the following line)... what's also evident is it's somehow no longer executing in the same current directory as now it can't find the files at all.

ld: file not found: car.swift

Again, when I comment out that line and hard-code the following, it works...

xcrun -sdk macosx swiftc -o "$OUTPUT_FILE" "main.swift" "car.swift" "Models/Road Bike.swift"

...so I know the files are in the right place. It just looks like xargs is no longer executing in the same directory.

Original Question

I'm trying to find the recommended way to pass the multi-line output of one command as individual arguments to a second command. 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

Attempt 1

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.

Attempt 2

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).

Attempt 3 - Success!

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.

CodePudding user response:

You're trying too hard.

find * -type f -name "*.swift" -print0 | xargs -0 xcrun -sdk macosx swiftc -o "$OUTPUT_FILE" 

CodePudding user response:

first

Save yourr multi-line output in an array

second

Pass the array to a desired command

example - test

Run line by line

# cd into a test directory
cd `mktemp -d`

# generate list of files
touch file-{01..09}.txt

# save the list in an array   remove trailing newlines 
mapfile -t list < <(find -type f -name \*.txt)

# check the output
echo "${lis[@]}"

# pass the array to a command you liked 
# for example substitute txt with TXT
sed 's/txt/TXT/g' <<< "${list[*]}"

# output
./file-09.TXT ./file-08.TXT ./file-07.TXT ./file-06.TXT ./file-05.TXT ./file-04.TXT ./file-03.TXT ./file-02.TXT ./file-01.TXT
  • Related