I use Bash 4.3.48(1) in Ubuntu 16.04 (xenial) with a LEMP stack.

I try to create a php.ini overridings file in a version agnostic way with printf.

1) The version agnostic operation fails:

printf "[PHP]\n post_max_size = 200M\n upload_max_filesize = 200M\n cgi.fix_pathinfo = 0" > /etc/php/*/fpm/zz_overrides.ini

The following error is given:

bash: /etc/php/*/zz_overrides.ini: No such file or directory

2) The version gnostic operation succeeds:

printf "[PHP]\n post_max_size = 200M\n upload_max_filesize = 200M\n cgi.fix_pathinfo = 0" > /etc/php/7.0/fpm/zz_overrides.ini

As you can see, both are basically identical besides * vs 7.0.

  • I didn't find a clue about this (regex?) problem in man printf.
  • I searched in Google and found nothing about "allowing regex in printf".

Why does the version agnostic operation fails and is there any bypass?

Edit: If possible, it is most important for me to use a one-line operation.

  • 2
    The * isn’t part of printf at all... – Jeff Schaller Jan 27 '18 at 18:58
  • Related: unix.stackexchange.com/questions/419964/… – pntshere Jan 27 '18 at 19:23
  • 1
    I though at first you meant agnostic to the shell version, but I think you meant the directories named by the PHP versions... Now, the question is, how does it fail? Do you have multiple directories that match that pattern? And if so, which one do you want to create the output file in? Or all of them? – ilkkachu Jan 27 '18 at 19:23
  • It is unlikely I'll have these, but I might have, so I'd prefer to cover such possible situation in advance. – pntshere Jan 27 '18 at 19:31
  • Given that you use Bash and you say you don't have multiple versions at the same time, it would seem it should work. What is the problem you face? – ilkkachu Jan 27 '18 at 21:18

The behavior of a pattern match in a redirection appears to differ between shells. Of the ones on my system, dash and ksh93 don't expand the pattern, so you get a file name with a literal *. Bash expands it(1), but only if the pattern matches one file. It complains if there are more filenames that match. Zsh works as if you gave multiple redirections, it redirects the output to all matching files.

(1) except when it's non-interactive and in POSIX mode

If you want the output to go to all matching files, you can use tee:

echo ... | tee /path/*/file > /dev/null

If you want it to go to only one file, the problem is to decide which one to use. If you want to check that there's only one file that matches the pattern, expand the whole list and count them.

In bash/ksh:

if [ "${#names[@]}" -gt 1 ] ; then
    echo "there's more than one"
    echo "there's only one: ${names[0]}"

In standard shell, set the positional parameters and use $# for the count.

Of course, if the pattern doesn't match any file, it's left as-is, and since the glob is in the middle, the result points to a nonexisting directory. It's the same as trying to create /path/to/file, before /path/to exists, it's just here we have /path/* instead, literally, with the asterisk.

To deal with that, you'd have to expand the directory name(s) without the filename, and then append the file name to all the directories. This is a bit ugly...

files=( "${dirs[@]/%/\/file}" )

and then we can use that array:

echo ... | tee "${files[@]}" > /dev/null

Or we could take the easy way out and loop over the filename pattern. It's a bit unsatisfactory in the more general case, since it requires running the main command once for each output file, or using a temporary file to hold the output.

for dir in /path/* ; do
    echo ... > "$dir/file"
  • 1
    For bash when non-interactive the behaviour depends on whether the posix mode is enabled or not. ksh88 does the expansion even when non-interactive (which makes it non-POSIX-compliant). Those two shells are the reasons why you also need to quote variables in arguments to redirections, to prevent globs from being expanded (> $file in those doesn't redirect to the file whose path is stored in $file but to the one file that matches the pattern in it if it matches only one file or gives an error otherwise) – Stéphane Chazelas Jan 27 '18 at 19:54
  • Sadly I was wrong, this didn't work: printf "[PHP]\n post_max_size = 200M\n upload_max_filesize = 200M\n cgi.fix_pathinfo = 0" | tee /etc/php/*/fpm/zz_overrides.ini. The error is that no such file or directory exist. Similarly to my original problem, using 7.0 does work... So it seems that even in tee I cannot use wildcards in Bash (Ubuntu server, 16.04, xenial, non-WSL). – pntshere Jan 28 '18 at 15:45
  • @pntshere, mm, since the file doesn't exist, the pattern doesn't match anything. It tries to write to /etc/php/*/fpm/zz_overrides.ini (literally), but can't, since /etc/php/*/ (with the literal asterisk) doesn't exist. See my latest edit. – ilkkachu Jan 28 '18 at 15:49
  • So only find and printf or for and printf will be the best matches here I guess. – pntshere Jan 28 '18 at 15:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.