How to loop through the sorted list of file names in two directories

时间:2017-12-18 06:20:40

标签: bash for-loop whitespace

Please note, i have read entries like For loop for files in multiple folders - bash shell and they ask for a significantly different thing.

I want to loop through the file names in a sorted order that exist in either of two directories. Files can potentially have spaces in them.

Let's say i have:

1/
  a
  a c b
  b
  c
2/
  a
  d

I would want to loop through: 'a', 'a c b', 'b', 'c', 'd'.

I have tried to do the following:

for fname in $((ls -1 -f -A "${dir1}"; ls -1 -f -A "${dir2}")|sort --unique); do
  echo "testing ${fname}"
done

the result then is

testing .
testing ..
testing a
testing a
testing c
testing b
testing b
testing c
testing d

For whatever reason i am getting '.' and '..' entries, that i was trying to exclude with -A, and also the file 'a c b' gets broken down into three strings.

I have tried to resolve it by adding --zero to the sort command, that changed nothing; by quoting the whole $(ls...|sort) part, and has resulted into a single entry into the for loop that has received the entire string with multiple lines each of which contained filename.

3 个答案:

答案 0 :(得分:3)

Do not consciously ever parse output of ls command(See Why you shouldn't parse the output of ls(1) ), it has lots of potential pitfalls. Use the find command with its -print0 option to null delimit the files so that file name with spaces/newline or any meta-charactetrs are handled and subsequently use GNU sort with the same null delimit character, to sort them alphabetically & remove duplicate files. If dir1 and dir2 are shell variables containing the names of the folders to look up, you can do

while IFS= read -r -d '' file; do
    printf '%s\n' "$file"
done< <(find "${dir1}" "${dir2}" -maxdepth 1 -type f -printf "%f\0" | sort -t / -u -z) 

答案 1 :(得分:2)

A much simpler approach might be to loop over everything and exclude duplicates by other means.

#!/bin/bash
# Keep an associative array of which names you have already processed
# Requires Bash 4
declare -A done
for file in 1/* 2/*; do
    base=${file#*/}  # trim directory prefix from value
    test "${done[$base]}" && continue
    : do things ...
    done["$base"]="$file"
done

答案 2 :(得分:1)

Answer:

  1. Change the for delimiter from whitespace to \n using the following command:

    IFS=$'\n'
    
  2. You used -l for ls which implies -a (and overrides -A); Use --color=never instead.

To summarize:

IFS=$'\n'
for fname in $((ls -1 --color=never -A "${dir1}"; ls -1 --color=never -A "${dir2}")|sort --unique); do
  echo "testing ${fname}"
done