0

I have a list of files as follows:

$ echo *
add akfefg aka ab ba

I want to echo file names containing both 'a' and 'b' in either order. I tried

echo *[ab]*[ba]*
aka ab ba

and it also prints 'aka'. I've tried

$ echo *a*b* && echo *b*a*
ab
ba

it does its job but 'ba' is printed in next line. Is there a simpler way to do this? Thank you.

wkde
  • 125

2 Answers2

5

In this answer, I'm addressing two different scenarios:

  1. You want to match the filename that contains both a and b in the current directory and then you want to do something to these files.
  2. You want to process the output of your two echo commands to remove the newlines.

If you want to loop over the filenames, then it would be good to have a single pattern that matches all names and then uses the expansion of that pattern in a loop. Using echo would not be advisable as this removes the distinction between individual filenames.

In the zsh shell, the single pattern (*a*b*|*b*a*) would expand to all names where the letters a and b occur in any order.

In the bash shell, you could use the single extended globbing pattern (or ksh-like globbing pattern) @(*a*b*|*b*a*) to do the same thing. The bash shell does not enable extended globbing patterns by default, so you would need to enable these with shopt -s extglob first.

In the bash shell, therefore, you could use something like

shopt -s extglob

for name in @(ab|ba); do # process "$name" here done

Another way to generate the wanted filenames for processing is by using find:

find . ! -name . -prune -name '*a*' -name '*b*'

This looks in the current directory (only) for any name that contains both a and b. You would then call some other utility using -exec at the end of this command to perform the operation, for example with

find . ! -name . -prune -name '*a*' -name '*b*' -exec sh -c '
    for name do
        # process "$name" here
    done' sh {} +

Here, find generates the filenames used by the in-line sh -c script. See also Understanding the -exec option of `find`

However, if this is a text-processing exercise and the purpose is to arrange two or more lines of text (the output from your two echo invocations) into a single line, then you'd do this by piping the text through paste -s -. This would additionally remove any newlines embedded in filenames. Using paste in this way rather than tr -d '\n' (which also removes newlines) ensures that the resulting text is terminated by a single newline at the end.

Kusalananda
  • 333,661
  • 1
    See also print -r -- *a*~^*b*(N) (or echo -E -) in zsh -o extendedglob to print space-separated and followed by a newline. Or print -r -- *a*~^*b*(N:q) for that list to be both human readable and post-processable. Or other quoting variants like () { print -r -- ${(q+)@}; } *a*~^*b*(N) – Stéphane Chazelas May 11 '22 at 09:00
2

No need to use square brackets they are for matching characters from a range. In the second example you used && operator, it's for executing multiple commands in sequence. That's why files are printed line by line.

Just run $ echo *a*b* *b*a*

That will print all files in a single line.

Edit for redundant matching like cacbca;

Extended globbing can be used, first enable it in bash $ shopt -s extglob

Then redifine the pattern as:

$ printf '%s ' @(*a*b*|*b*a*)

@(pattern-list) Matches one of the given patterns

Kadir
  • 264
  • 1
  • 6
  • 2
    Beside that echo should be changed to printf. There is the issue that cacbca will be printed twice. And, after all files are joined by spaces there is no clean way to divide that string into filenames again if those filenames could contain spaces and/or newlines. –  May 11 '22 at 05:16
  • Thank you for the answers. Is there a way to use echo to do the job instead of printf though? Thank you. – wkde May 11 '22 at 05:24
  • 1
    No, echo is the wrong tool for this job. –  May 11 '22 at 05:35
  • 1
    The problem with cacbca can be solved by using find in the first place. The question asks for a simpler way. find is not simpler, but it's probably a proper way. – Kamil Maciorowski May 11 '22 at 05:36