0

I have a bash script:

for j in "$(ls -d */)"; do 
    echo "$j"
    echo "$j"
done

and this outputs:

dir1/
dir2/
dir3/
dir1/
dir2/
dir3/

What I want it to output is this:

dir1/
dir1/
dir2/
dir2/
dir3/
dir3/

How do I get it to output this way?

(This is a toy example, what I really want it to do is cd into the directory, do some stuff, and cd back out, which I realize I can probably do the equivalent with some string concatenation but this behavior is so baffling to me and I couldn't find any other questions on it so I thought I would ask.)

0

Parsing the output of ls as you're doing is not a good way to do this.

But with that said, your problem is the quoting:

rm -rf dir?
for j in 1 2 3; do mkdir dir$j; done
for j in $(ls -d */); do 
    echo "$j"
    echo "$j"
done
dir1//
dir1//
dir2//
dir2//
dir3//
dir3//

With quotes as your post has, the entire output of ls is enclosed in quotes, so therefore it is treated as one single argument, causing the for loop to iterate only once, with the entire output ls as an argument. Your code dutifully prints the entire output of ls twice.

Rather than using ls, a better way to construct your example would be:

find . -type d -maxdepth 1 -mindepth 1 -exec printf '%s\n%s\n' "{}" "{}" \;
  • 2
    removing the quotes from around $(ls) helps only as long as no directory name contains whitespace (let alone glob characters). Holiday 2019/ would show up as two strings, etc. – ilkkachu Oct 25 '19 at 21:15
  • @ilkkachu Agreed. Hence my comment that parsing ls is not a good method for this, and my inclusion of a better method which does handle whitespace and glob characters. However, when the OP asks why their output is ordered the way it is, the answer to that question is, because the OP quoted the ls output. – Jim L. Oct 25 '19 at 21:40
4

Your loop does exactly one iteration. In this iteration, the variable j has the value

dir1/
dir2/
dir3/

and you output it twice.

The variable gets this value since you are iterating over a single string, the output of ls -d */.

To loop over the subdirectories, use

for dirpath in */; do
    printf '%s\n' "$dirpath"
    printf '%s\n' "$dirpath"
done

That is, there is no need to use ls at all (ls is for visual inspection only).

To cd into a directory in each iteration, do the cd in a subshell together with whatever operation you need to do there. That way you don't have to cd back, as the current working directory is local to the subshell:

for dirpath in */; do
    ( cd -- "$dirpath" && some-command )
done

It's only ever necessary to cd into the directory if you need to change the current working directory for whatever some-command is though. Most things would be able to be done from the original working directory.

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.