2

When using backticks (in perl) i seem to have some sort of inconsistency with brace expansion

print `ls -d ~/{a,b}`;

Works fine, but when i try

print `ls -d ~/{a}`;

I get:

ls: cannot access /home/user/{a}: No such file or directory

I have tried all sort of quoting, spacing and escapes but to no avail. my question is, is there a way to force the expansion? I realize I can avoid the issue all together if I just glob the stuff myself, but I'm curious about this behavior

i tried this under both bash and tcsh. when used directly under tcsh, the command works, but under bash it doesn't

brian d foy
  • 129,424
  • 31
  • 207
  • 592
Nullman
  • 4,179
  • 2
  • 14
  • 30

2 Answers2

8

Now, after your several edits, I understand that you may be using two different shells. And, also, that the one calling the shell is perl, not you.

So, to cover the three elements:

bash

In bash. A simple {a} will not be expanded:

$ echo ~/{a,b}
/home/user/a /home/user/b

$ echo ~/{a}
/home/user/{a}

Which is searched as a file by ls, and not found.

The relevant part of the manual man bash is (emphasis mine):

A correctly-formed brace expansion must contain unquoted opening and closing braces, and at least one unquoted comma or a valid sequence expression.

A {a} is lacking a required comma.

The only alternative is to actually use

$ print `ls -d ~/a`

tcsh

In tcsh: A simple {a} will be expanded (as in a csh shell):
Using > as shell indicator for tcsh prompt:

> echo --{a,b}++
 --a++ --b++

> echo --{a}++
--a++

As a simple {} also will:

$ echo --{}++
 --++

The only braces that will not be removed will be {} and { and }.
IIF (if and only if) they are recognized as "words". Most commonly surrounded by spaces:

$ echo --{}++ == {} .. { :: } aa { bb
--++ == {} .. { :: } aa { bb

If a { or a } appear as part of a word, without the matching brace, is an error:

> echo aa{bb
Missing '}'.

The only description of this on the man tcsh is really short (unclear?):

> As a special case the words `{', `}' and `{}' are passed undisturbed.

perl

If the shell commands are called from perl, in the cases that do call a shell, like under Unix systems, the shell called is /bin/sh.

An an additional twist, the /bin/sh is served by bash in most linux systems, by tcsh in FreeBSD and by ksh or csh in OpenBSD.

It becomes difficult to predict the output of a perl called /bin/sh.

You could make sure that an specific shell is called:

#!/usr/bin/perl
print `/bin/tcsh -c 'ls -d ~/{a,b}'`;

Now the question remains:

  • Why do you need to use brace expansion on a single character?
Community
  • 1
  • 1
  • unfortunately i wont have access to the system untill next week, but i understand the issue is that doing 'ls -d ~/{a}' in the shell actually works. i will mark this as solved for now and update next week. thanks! – Nullman May 16 '17 at 11:14
  • Re "*doing 'ls -d ~/{a}' in the shell actually works*", Did you actually use `/bin/sh`? – ikegami May 16 '17 at 16:40
  • @arrow upon further inspection it seems that under tcsh the ls works, but under bash it does NOT. why though, while using perl under tcsh doesnt it work? – Nullman May 21 '17 at 11:39
  • Perl doesn't care what shell you are using. Backticks uses /bin/sh. – brian d foy May 21 '17 at 15:51
  • @Nullman Expanded sections for tcsh and perl. All the details should be clear now. Any additional question?. –  May 22 '17 at 12:58
  • @Arrow excellent and complete answer! the answer to your question is that i dont! im not even using ls. i just gave that as a simple example to a more complex command invocation :) – Nullman May 22 '17 at 18:13
  • Really, the answer to @isaac's question (because I've encountered this issue before) is that sometimes the pattern inside the curries is programmatically created, e.g. by a join on a list of items, where the size of the list is variable/unpredictable. I'm also a tcsh user and to me, I agree with OP that bash brace expansion is inconsistent. Not only do you have to create a special case for an empty list, you have to create one for a list size of 1. Incidentally OP, I use `bsd_glob` wrapped in some customizing code for the remaining quirks for all shell interpolations in Perl. – hepcat72 Jun 20 '21 at 13:53
2

When you are having problems with shell commands, try them outside of Perl (or any program) to see what they do:

Bash:

$ ls ~/{a}
ls: /Users/brian/{a}: No such file or directory
$ ls ~/{a,b}
ls: /Users/brian/a: No such file or directory
ls: /Users/brian/b: No such file or directory

Tsch:

% ls ~/{a}
ls: /Users/brian/a: No such file or directory
% ls ~/{a,b}
ls: /Users/brian/a: No such file or directory
ls: /Users/brian/b: No such file or directory

So, you see that the two shells expand globs differently. From the bash docs:

A correctly-formed brace expansion must contain unquoted opening and closing braces, and at least one unquoted comma or a valid sequence expression. Any incorrectly formed brace expansion is left unchanged.

From the tcsh docs is a bit lacking in that it doesn't mention that you don't need the comma for expansion:

The metanotation 'a{b,c,d}e' is a shorthand for 'abe ace ade'. Left-to-right order is preserved: '/usr/source/s1/{oldls,ls}.c' expands to '/usr/source/s1/oldls.c /usr/source/s1/ls.c'. The results of matches are sorted separately at a low level to preserve this order: '../{memo,*box}' might expand to '../memo ../box ../mbox'. (Note that 'memo' was not sorted with the results of matching '*box'.) It is not an error when this construct expands to files which do not exist, but it is possible to get an error from a command to which the expanded list is passed. This construct may be nested. As a special case the words '{', '}' and '{}' are passed undisturbed.

Now, inside Perl, the backticks use /bin/sh. It doesn't care which shell you were in when you started the perl process. From perlop:

A string which is (possibly) interpolated and then executed as a system command with /bin/sh or its equivalent.

That's why you see the bash behavior. However, you can use any shell that you like (and is available on the system):

my $output = `tcsh -c 'ls -l ~/{a}'`
brian d foy
  • 129,424
  • 31
  • 207
  • 592
  • upon further read of http://stackoverflow.com/questions/4225102/which-shell-does-a-perl-system-call-use it seems you are correct! thank you! – Nullman May 21 '17 at 15:13
  • 1
    Well, since I cite the actual perl docs, I'm also technically correct, which is the best kind of correct. https://www.youtube.com/watch?v=hou0lU8WMgo – brian d foy May 21 '17 at 15:26