Question

I noticed today Bash printf has a -v option

-v var    assign the output to shell variable VAR rather than
          display it on the standard output

If I invoke like this it works

$ printf -v var "Hello world"

$ printf "$var"
Hello world

Coming from a pipe it does not work

$ grep "Hello world" test.txt | xargs printf -v var
-vprintf: warning: ignoring excess arguments, starting with `var'

$ grep "Hello world" test.txt | xargs printf -v var "%s"
-vprintf: warning: ignoring excess arguments, starting with `var'
Was it helpful?

Solution

xargs will invoke /usr/bin/printf (or wherever that binary is installed on your system). It will not invoke bash's builtin function. And only a builtin (or sourcing a script or similar) can modify the shell's environment.

Even if it could call bash's builtin, the xargs in your example runs in a subsell. The subshell cannot modify it's parent's environment anyway. So what you're trying cannot work.

A few options I see if I understand your sample correctly; sample data:

$ cat input
abc other stuff
def ignored
cba more stuff

Simple variable (a bit tricky depending on what exactly you want):

$ var=$(grep a input)
$ echo $var
abc other stuff cba more stuff
$ echo "$var"
abc other stuff
cba more stuff

With an array if you want individual words in the arrays:

$ var=($(grep a input))
$ echo "${var[0]}"-"${var[1]}"
abc-other

Or if you want the whole lines in each array element:

$ IFS=$'\n' var=($(grep a input)) ; unset IFS
$ echo "${var[0]}"-"${var[1]}"
abc other stuff-cba more stuff

OTHER TIPS

There are two printf's - one is a shell bultin and this is invoked if you just run printf and the other is a regular binary, usually /usr/bin/printf. The latter doesn't take a -v argument, hence the error message. Since printf is an argument to xargs here, the binary is run, not the shell bulitin. Additionally, since it's at the receiving end of a pipeline, it is run as a subprocess. Variables can only be inherited from parent to child process but not the other way around, so even if the printf binary could modify the environment, the change wouldn't be visible to the parent process. So there are two reasons why your command cannot work. But you can always do var=$(something | bash -c 'some operation using builtin printf').

Mat gives an excellent explanation of what's going on and why.

If you want to iterate over the output of a command and set a variable to successive values using Bash's sprintf-style printf feature (-v), you can do it like this:

grep "Hello world" test.txt | xargs bash -c 'printf -v var "%-25s" "$@"; do_something_with_formatted "$var"' _ {} \;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top