1

I have a variable in bash, which is something like

filenames='file 1
file 2
file 3'

I need to send each line in the above variable's content as a single argument to a program. But I can't get bash to that. Here's what I tried:

python -c 'import sys; print sys.argv' $filenames
['-c', 'file', '1', 'file', '2', 'file', '3']

or

python -c 'import sys; print sys.argv' "$filenames"
['-c', 'file 1\nfile 2\nfile 3']

What I'm expecting is something like this

['-c', 'file 1', 'file 2', 'file 3']

I've tried fiddling with the IFS setting too, but couldn't get it right. Any ideas on this?

5 Answers 5

2

Double-evaluating a command with either eval or sh -c can lead to weird bugs if you don't get the extra level of quoting just right. I'd recommend using an array instead, either just by storing the file list as an array to begin with:

filearray=("file 1" "file 2" "file 3")
python -c 'import sys; print sys.argv' "${filearray[@]}"

or by converting it from newline-delimited string to an array:

filenames='file 1
file 2
file 3'
oldIFS="$IFS"; IFS=$'\n'
filearray=($filenames)
IFS="$oldIFS"
python -c 'import sys; print sys.argv' "${filearray[@]}"
Sign up to request clarification or add additional context in comments.

1 Comment

+1 This is the perfect solution I've been wanting. Thanks a bunch.
2
$ export filenames="file 1
file 2
file 3"
$ echo "$filenames" | xargs -d\\n python -c 'import sys; print sys.argv'
['-c', 'file 1', 'file 2', 'file 3']

Another way using read instead of xargs:

#!/bin/bash
filenames="file 1
file 2
file 3"
cmd="python -c 'import sys; print sys.argv; print sys.stdin.readlines()'"
while read file
do
    args="$args \"$file\""
done < <(echo "$filenames")
echo $cmd $args
echo `echo hi | sh -c "$cmd $args"`

Output:

$ ./test.sh 
python -c 'import sys; print sys.argv; print sys.stdin.readlines()' "file 1" "file 2" "file 3"
['-c', 'file 1', 'file 2', 'file 3'] ['hi\n']

4 Comments

whoa! I thought xargs runs the command once for each line of input, no?
Also, I need something else to be going in the stdin of the python command, so I don't think this'll do it in its current form.
Nope, you would need to use the -n1 option to get that behavior. By default it uses as many arguments as will fit in a line for each invocation (you can redefine "fit" with the -s and -n options).
Ah, eval. I had a feeling this'd be the solution. Also, the while loop can be turned to this: args="$(echo "$filenames" | sed 's/^\|$/"/g' | tr '\n' ' ')" :)
1

Just to add another option,

OLDIFS=$IFS
IFS='
'
set -- $filenames
python -c 'import sys; print sys.argv; print sys.stdin.readlines()' "$@"
IFS=$OLDIFS

Edit: Add code to save old value in OLDIFS and restore it afterwards. IFS='whatever' set -- $var on a single line does not work.

3 Comments

And I was trying IFS="\n" and the like, #facepalm. Thanks.
In bash, $'\n' is a literal newline.
Hm, I remember trying that too. Oh well.
0

Yet another option:

while read line ; do
  python -c 'import sys; print sys.argv' $line;
done <(echo $filenames)

1 Comment

Wouldn't any newlines be mutilated because of the echo without quoting?
0

What about xargs:

$ seq 10 | xargs  python -c 'print __import__("sys").argv'
['-c', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10']

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.