简体   繁体   中英

Bash - Brace expansion on result of command substitution?

I want to prepend a string to all the files in a directory. What I want to do is something like:

echo string{$(ls some_dir)}

This won't work because ls separates words with spaces, and brace expansion requires commas. So I thought I'd use tr to replace the spaces with commas, like:

echo string{$(ls some_dir) | tr ' ' ','}

But that doesn't work either because the pipe takes precedence.

What's the correct way to do this? I know I could probably use a sane language like Python, but it's frustrating that Bash can't even do something as simple as that.

If you really want to interpolate the contents of a directory (which is what $(ls some_dir) would give you) then you can do

printf 'string%s ' some_dir/*

IRL, you probably want it to end with a newline.

{ printf 'string%s ' some_dir/*; echo; }

You can generalize this to the output of any glob or brace expansion:

printf 'foo%d\n' {11..22}

Edit

Based on your comment, you want to eliminate the "some_dir/" part, you can't merely do that with printf . You can either cd to the directory so the globs expand as desired, or use parameter expansion to clean up the leading directory name:

( cd some_dir && printf 'string%s ' *; echo )

or

{ cd some_dir && printf 'string%s ' * && cd - >/dev/null; echo; }

or

names=( some_dir/* ) names=( "${names[@]#some_dir/}" )
{ printf 'string%s ' "${names[@]}"; echo; }

One way to do it, which will deal gracefully with whitespace in filenames:

files=("$dir"/*); files=("${files[@]/#"$dir"\//"$prefix"}")

That will store the prefixed strings in the array $files ; you could iterate over them using an array expansion:

for file in "${files[@]}"; do 
  # Something with file
done

or print them out using printf :

printf "%s\n" "${files[@]}"

The advantage of using the array expansion is that it does not involve word-splitting, so even if the elements have whitespace in them, the array expansion will contain each element as a single word.

Of course, bash can do it.

Let's go step by step.

1. fix an issue in your second example

This is your second example

echo string{$(ls some_dir) | tr ' ' ','}

You put pipe outside the command substitution, which is totally wrong.

I believe you want to pipe the stream from ls output to tr input, so it's obvious that the pipe is supposed to be put inside the command substitution, like this

echo string{$(ls some_dir | tr ' ' ',')}

2. output of ls is separated by newline rather than whitespace

so here we go

echo string{$(ls some_dir | tr '\n' ',')}

3. brace expansion is performed prior to command substitution

In the other word, after command substitution is expanded to f1,f2,f3,d1, , the brace expansion will not be performed any more.

So, no doubt, the command will print string{f1,f2,f3,d1,} .

The solution is letting bash evaluate it again.

eval echo string{$(ls some_dir | tr '\n' ',')}

OK, up to now, the result looks very good (try it yourself, you'll get it), it is very close to what you were looking for, except one tiny spot.

You may already noticed the comma at the end of the output I demonstrated above. The comma results in an unnecessary string appearing at the end of the final output.

So let's make it done.

4. remove the ending comma

eval echo string{$(echo -n "$(ls some_dir)" | tr '\n' ',')}

OK, this is it.


Oh... BTW., this is just an specific solution for your specific question. You may develop new variants of your question, and this specific solution may not fit your new question. If so, I suggest you run man bash , and read it from head to toe very very carefully, then you will become unstoppable.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM