Сценарии оболочки, цикл по папкам

1

У меня есть большая серия подпапок на моем Mac со случайным количеством CSV-файлов в каждом. То, что я хотел бы сделать, это объединить эти файлы в один файл для каждого каталога.

До сих пор я знаю, что могу объединить эти файлы cat * > mergedfile.csv, но у меня есть проблемы, перебирающие все папки. Мне до сих пор удавалось объединить все виды вещей, но я не могу заставить это делать то, что я хочу точно.

Есть идеи, как лучше всего это сделать?

for DIR in ./subfolder/*
do
    cat $dir/* > merged.csv 
done
Дэвид М
источник
1
$DIRи $dirне одно и то же.
Чороба

Ответы:

4

С помощью findвы можете рекурсивно перечислить все файлы, которые соответствуют определенному критерию, например, имя файла.

for file in $(find . -type f -name "*.csv"); do cat "$file" >> /path/to/output.csv; done

Разбив его, find . -name "*.csv"вы найдете все файлы CSV из текущей папки, в которой вы находитесь ( .), и цикл просто перебирает этот список, добавляя все в output.csvфайл.

Но: Имена файлов с пробелами, заглушающими символами и символами новой строки здесь могут быть сложными. Более безопасным решением было бы просто использовать execкоманду find.

find . -name "*.txt" -exec cat '{}' >> /path/to/output.csv ';'

Здесь '{}'будет заменен поиск с именем файла. Длинные вопросы и ответы о том, почему это так и как обойти проблему, можно найти здесь .

Теперь, если вы хотите создать по одному CSV-файлу для каждого каталога - извините, раньше этого не видели - я бы, вероятно, сделал что-то вроде этого:

for dir in $(find . -type d); do find $dir -maxdepth 1 -name "*.csv" -exec cat {} >> "$dir/out" ';'; mv "$dir/out" "$dir/merged.csv"; done

Хотя приведенное ниже решение Франка, вероятно, более эффективно.


Конечно, обратите внимание на разницу между >и >>. Первый всегда обрезает файл до нулевой длины перед записью в него, тогда как второй просто добавляет файл.

Причина, по которой cat *.csv > merged.csvсработало и почему в вашем цикле это не сработает, заключается в том, что оболочка будет расширять подстановочный знак раньше, поэтому в основном она видит:

cat file1.csv file2.csv file3.csv > merged.csv

... что, конечно, ничего не перезаписывает.

slhck
источник
1

В родительскую папку:

for dir in $(find . -type d); do
  cd $dir
  [[ $(ls *.csv|wc -l) -eq 0 ]] 2> /dev/null || { print "$dir.csv created";
                                                  cat *.csv > $dir.csv; }
  cd - > /dev/null
done
Франк Лефорт
источник
1

Предполагая bash 4+ (проверьте с помощью bash --version), вы можете активировать globstar с помощью shopt -s globstarи циклически перебирать все каталоги (и только каталоги - конечные /файлы исключаются) рекурсивно с**/

for f in **/; do cat "$f"/*.csv > "$f"/merged.csv; done

Если вы действительно хотите использовать все файлы в каталоге, а не только те, которые заканчиваются на .csv, то

for f in **/; do cat "$f"/* > "$f"/merged.csv; done

Если вы хотите перейти только на один уровень, а не быть полностью рекурсивным, используйте */вместо **/.

Ключевая ошибка в OP-скрипте (не говоря уже о том, что bash чувствителен к регистру) заключается в том, что он пытается записать содержимое всех файлов в один .csvфайл и делает это таким образом, что каждая итерация цикла завершается напиши последнее.

Если вы хотите , чтобы объединить все те .csvфайлы рекурсивно в один файл, вы можете снова использовать globstar

for f in **/*.csv; do cat "$f" > merged_all.csv
evilsoup
источник