Як я можу порахувати файли з конкретним розширенням та каталоги, в яких вони перебувають?


14

Хочу знати, скільки регулярних файлів мають розширення .cу великій складній структурі каталогів, а також скільки каталогів ці файли поширюються по всій. Я хочу отримати лише два числа.

Я бачив це питання про те, як отримати кількість файлів, але мені потрібно знати кількість каталогів, в яких також є файли.

  • Мої імена файлів (включаючи каталоги) можуть мати будь-які символи; вони можуть починатися з .або -мати пробіли або нові рядки.
  • У мене можуть бути кілька посилань, назви яких закінчуються .c, і посилання на каталоги. Я не хочу, щоб символічні посилання переглядалися чи рахувалися, або я, принаймні, хочу знати, чи і коли вони рахуються.
  • Структура каталогів має багато рівнів, а каталог верхнього рівня (робочий каталог) містить принаймні один .cфайл.

Я поспішно написав кілька команд в оболонці (Bash), щоб їх сам порахувати, але не думаю, що результат є точним ...

shopt -s dotglob
shopt -s globstar
mkdir out
for d in **/; do
     find "$d" -maxdepth 1 -type f -name "*.c" >> out/$(basename "$d")
done
ls -1Aq out | wc -l
cat out/* | wc -l

Це виводить скарги на неоднозначні переадресації, пропускає файли у поточному каталозі та видаляє спеціальні символи (наприклад, перенаправлений findвихід друкує нові рядки у назви файлів ) та записує цілу купу порожніх файлів (ой).

Як я можу надійно перерахувати свої .cфайли та файли, що містять їх?


Якщо це допомагає, ось кілька команд для створення тестової структури з неправильними іменами та посиланнями:

mkdir -p cfiles/{1..3}/{a..b} && cd cfiles
mkdir space\ d
touch -- i.c -.c bad\ .c 'terrible
.c' not-c .hidden.c
for d in space\ d 1 2 2/{a..b} 3/b; do cp -t "$d" -- *.c; done
ln -s 2 dirlink
ln -s 3/b/i.c filelink.c

У отриманій структурі 7 каталогів містять .cфайли, а 29 звичайних файлів закінчуються .c(якщо dotglobвимкнено, коли команди виконуються) (якщо я зробив неправильний рахунок, будь ласка, повідомте мене про це). Це числа, які я хочу.

Будь ласка, не користуйтеся цим тестом.

Примітка: Відповіді будь-якою оболонкою чи іншою мовою будуть перевірені та оцінені мною. Якщо мені доведеться встановити нові пакети, немає проблем. Якщо ви знаєте рішення GUI, я рекомендую вам поділитися (але я не можу піти так далеко, щоб встановити цілий DE для його тестування) :) Я використовую Ubuntu MATE 17.10.


Написання програми для боротьби з шкідливими звичками до програмування виявилося досить складним;)
WinEunuuchs2Unix

Відповіді:


16

Я не досліджував вихід із символьними посиланнями, але:

find . -type f -iname '*.c' -printf '%h\0' |
  sort -z |
  uniq -zc |
  sed -zr 's/([0-9]) .*/\1 1/' |
  tr '\0' '\n' |
  awk '{f += $1; d += $2} END {print f, d}'
  • findКоманда виводить ім'я директорії кожного .cфайлу він знаходить.
  • sort | uniq -cдасть нам кількість файлів у кожному каталозі (тут sortможе бути непотрібним, не впевнений)
  • з sed, я замінюю ім'я каталогу на 1, тим самим усуваючи всі можливі дивні символи, з лише кількістю та 1залишком
  • що дозволяє мені перетворити на новий рядок, виведений за допомогою tr
  • який я потім підсумовую awk, щоб отримати загальну кількість файлів та кількість каталогів, які містили ці файли. Зауважте, що dтут по суті те саме, що і NR. Я міг опустити вставку 1в sedкоманді та просто надрукувати NRтут, але я думаю, що це трохи зрозуміліше.

До тих пір tr, поки дані не будуть обмежені NUL, захищені від усіх дійсних імен файлів.


За допомогою zsh та bash ви можете використати printf %qдля отримання рядка, котируемого в якому не було б нових рядків. Отже, ви можете зробити щось на кшталт:

shopt -s globstar dotglob nocaseglob
printf "%q\n" **/*.c | awk -F/ '{NF--; f++} !c[$0]++{d++} END {print f, d}'

Однак, незважаючи на те **, що не передбачається розширення для посилань на каталоги , я не зміг отримати бажаний вихід на bash 4.4.18 (1) (Ubuntu 16.04).

$ shopt -s globstar dotglob nocaseglob
$ printf "%q\n" ./**/*.c | awk -F/ '{NF--; f++} !c[$0]++{d++} END {print f, d}'
34 15
$ echo $BASH_VERSION
4.4.18(1)-release

Але zsh добре працював, і команду можна спростити:

$ printf "%q\n" ./**/*.c(D.:h) | awk '!c[$0]++ {d++} END {print NR, d}'
29 7

Dдозволяє це Glob вибрати точкові файли, .вибирає звичайні файли (так, чи не Symlinks), і :hдрукує тільки шлях до каталогу , а не ім'я файлу (наприклад , find«s %h) (див розділи по імені файлу Генерація та модифікаторами ). Таким чином, за допомогою команди awk нам просто потрібно порахувати кількість унікальних каталогів, що з’являються, а кількість рядків - це кількість файлів.


Це круто. Використовує саме те, що потрібно, і не більше. Дякую за навчання :)
Zanna

@Zanna, якщо ви опублікуєте кілька команд, щоб відтворити структуру каталогів із посиланнями, і очікуваний вихід із символьними посиланнями, я можу це виправити відповідно.
муру

Я додав декілька команд для створення (зайво складної, як зазвичай) тестової структури з посиланнями.
Занна

@Zanna Я думаю, що ця команда не потребує жодних коригувань для отримання 29 7. Якщо додати -Lдо find, який йде до 41 10. Який вихід потрібен?
муру

1
Додано метод zsh + awk. Напевно, є якийсь спосіб отримати zsh, щоб надрукувати рахунок для мене, але не знаю як.
муру

11

Python має os.walk, що робить такі завдання легкими, інтуїтивно зрозумілими та автоматично надійними, навіть перед обличчям дивних імен файлів, таких як символи нового рядка. Цей скрипт Python 3, який я спочатку розміщував у чаті , призначений для запуску в поточному каталозі (але він не повинен розташовуватися в поточному каталозі, і ви можете змінити шлях, який він проходить os.walk):

#!/usr/bin/env python3

import os

dc = fc = 0
for _, _, fs in os.walk('.'):
    c = sum(f.endswith('.c') for f in fs)
    if c:
        dc += 1
        fc += c
print(dc, fc)

Це друкує кількість каталогів, які безпосередньо містять щонайменше один файл, ім'я якого закінчується .c, а потім пробіл, а за ним - кількість файлів, імена яких закінчуються .c. "Приховані" файли - тобто файли, імена яких починаються з - .включаються, а сховані каталоги аналогічно проходять.

os.walkрекурсивно перетинає ієрархію каталогів. У ньому перераховані всі каталоги, які рекурсивно доступні з початкової точки , ви даєте його, отримуючи інформацію про кожного з них , як кортеж з трьох значень, root, dirs, files. Для кожного каталогу, в який він переходить (включаючи першого, ім'я якого ви даєте йому):

  • rootмістить ім'я шляху цього каталогу. Зверніть увагу , що це НЕ має ніякого відношення до «кореневого каталогу» системи /(а також не пов'язане з /root) хоча б йти до тих , якщо ви починаєте там. У цьому випадку, rootпочинається з шляху - .то, поточний каталог - і йде скрізь під ним.
  • dirsмістить список імен шляхів усіх підкаталогів каталогу, ім'я якого зараз знаходиться root.
  • filesмістить список імен шляхів усіх файлів, що перебувають у каталозі, ім'я якого зараз міститься, rootале вони самі по собі не є каталогами. Зауважте, що це включає в себе інші типи файлів, ніж звичайні файли, включаючи символічні посилання, але це здається, що ви не очікуєте, що такі записи закінчуються, .cі вам цікаво бачити будь-які подібні записи .

У цьому випадку мені потрібно лише вивчити третій елемент кортежу files(який я називаю fsв сценарії). Як і findкоманда, Python os.walkдля мене переходить у підкаталоги; Єдине, що мені доводиться перевіряти - це назви файлів, що містяться в кожному з них. На відміну від findкоманди, однак, os.walkавтоматично надає мені список цих імен.

Цей сценарій не дотримується символічних посилань. Ви, ймовірно , не хочете, щоб символьні посилання виконувались для такої операції, тому що вони могли утворювати цикли, і навіть якщо циклів немає, однакові файли та каталоги можуть проходити і перераховуватися кілька разів, якщо вони доступні через різні символьні посилання.

Якщо ви коли-небудь хотіли os.walkдотримуватися символьних посилань - яких ви зазвичай не хотіли - тоді можете перейти followlinks=trueдо цього. Тобто замість того, щоб писати, os.walk('.')можна було писати os.walk('.', followlinks=true). Я повторюю, що ви рідко цього хочете, особливо для такої задачі, де ви рекурсивно перераховуєте всю структуру каталогів, незалежно від того, наскільки вона велика, і підраховуєте всі файли в ній, які відповідають певним вимогам.


7

Знайти + Perl:

$ find . -type f -iname '*.c' -printf '%h\0' | 
    perl -0 -ne '$k{$_}++; }{ print scalar keys %k, " $.\n" '
7 29

Пояснення

findКоманда знайде будь-які звичайні файли (так що ніяких символічних посилань або каталогів) , а потім надрукувати ім'я каталогу , вони знаходяться в ( %h) , а потім \0.

  • perl -0 -ne: прочитайте рядок введення за рядком ( -n) та застосуйте сценарій, заданий -eкожним рядком. Встановлює -0роздільник рядка вводу \0таким чином, щоб ми могли читати введені з нулем значення.
  • $k{$_}++: $_- це спеціальна змінна, яка приймає значення поточного рядка. Він використовується як ключ до хеша %k , значення якого - кількість разів, коли кожен рядок введення (ім'я каталогу) було переглянуто.
  • }{: це скорочений спосіб написання END{}. Будь-які команди після }{заповіту будуть виконані один раз, після того як усі дані будуть оброблені.
  • print scalar keys %k, " $.\n": keys %kповертає масив ключів у хеші %k. scalar keys %kдає кількість елементів у цьому масиві, кількість переглянутих каталогів. Це друкується разом із поточним значенням $.спеціальної змінної, яка містить номер поточного вхідного рядка. Оскільки це запускається в кінці, поточним номером рядка введення буде номер останнього рядка, тому кількість рядків, проглянутих до цього часу.

Ви можете розширити команду perl для цього для наочності:

find  . -type f -iname '*.c' -printf '%h\0' | 
    perl -0 -e 'while($line = <STDIN>){
                    $dirs{$line}++; 
                    $tot++;
                } 
                $count = scalar keys %dirs; 
                print "$count $tot\n" '

4

Ось моя пропозиція:

#!/bin/bash
tempfile=$(mktemp)
find -type f -name "*.c" -prune >$tempfile
grep -c / $tempfile
sed 's_[^/]*$__' $tempfile | sort -u | grep -c /

Цей короткий скрипт створює тимчасовий файл, знаходить кожен файл у та під поточним каталогом, що закінчується, .cі записує список у темпфіл. grepПотім використовується для підрахунку файлів (наступний Як я можу отримати кількість файлів у каталозі за допомогою командного рядка? ) двічі: Другий раз каталоги, які перераховані декілька разів, видаляються за допомогою sort -uзнімання імен файлів з кожного рядка за допомогою sed.

Це також належним чином працює з новими рядками у назви файлів: grep -c /підраховує лише рядки з косою рисою і тому враховує лише перший рядок імені файлу багато рядків у списку.

Вихідні дані

$ tree
.
├── 1
   ├── 1
      ├── test2.c
      └── test.c
   └── 2
       └── test.c
└── 2
    ├── 1
       └── test.c
    └── 2

$ tempfile=$(mktemp);find -type f -name "*.c" -prune >$tempfile;grep -c / $tempfile;sed 's_[^/]*$__' $tempfile | sort -u | grep -c /
4
3

4

Маленька оболонка

Я пропоную невеликий оболонку bash з двома основними командними рядками (та змінною, filetypeщоб полегшити комутацію, щоб шукати інші типи файлів).

Він не шукає символів або посилань, а лише звичайні файли.

#!/bin/bash

filetype=c
#filetype=pdf

# count the 'filetype' files

find -type f -name "*.$filetype" -ls|sed 's#.* \./##'|wc -l | tr '\n' ' '

# count directories containing 'filetype' files

find -type d -exec bash -c "ls -AF '{}'|grep -e '\.'${filetype}$ -e '\.'${filetype}'\*'$ > /dev/null && echo '{} contains file(s)'" \;|grep 'contains file(s)$'|wc -l

Докладний оболонку

Це більш докладна версія, яка також враховує символічні посилання,

#!/bin/bash

filetype=c
#filetype=pdf

# counting the 'filetype' files

echo -n "number of $filetype files in the current directory tree: "
find -type f -name "*.$filetype" -ls|sed 's#.* \./##'|wc -l

echo -n "number of $filetype symbolic links in the current directory tree: "
find -type l -name "*.$filetype" -ls|sed 's#.* \./##'|wc -l
echo -n "number of $filetype normal files in the current directory tree: "
find -type f -name "*.$filetype" -ls|sed 's#.* \./##'|wc -l
echo -n "number of $filetype symbolic links in the current directory tree including linked directories: "
find -L -type f -name "*.$filetype" -ls 2> /tmp/c-counter |sed 's#.* \./##' | wc -l; cat /tmp/c-counter; rm /tmp/c-counter

# list directories with and without 'filetype' files (good for manual checking; comment away after test)
echo '---------- list directories:'
 find    -type d -exec bash -c "ls -AF '{}'|grep -e '\.'${filetype}$ -e '\.'${filetype}'\*'$ > /dev/null && echo '{} contains file(s)' || echo '{} empty'" \;
echo ''
#find -L -type d -exec bash -c "ls -AF '{}'|grep -e '\.'${filetype}$ -e '\.'${filetype}'\*'$ > /dev/null && echo '{} contains file(s)' || echo '{} empty'" \;

# count directories containing 'filetype' files

echo -n "number of directories with $filetype files: "
find -type d -exec bash -c "ls -AF '{}'|grep -e '\.'${filetype}$ -e '\.'${filetype}'\*'$ > /dev/null && echo '{} contains file(s)'" \;|grep 'contains file(s)$'|wc -l

# list and count directories including symbolic links, containing 'filetype' files
echo '---------- list all directories including symbolic links:'
find -L -type d -exec bash -c "ls -AF '{}' |grep -e '\.'${filetype}$ -e '\.'${filetype}'\*'$ > /dev/null && echo '{} contains file(s)' || echo '{} empty'" \;
echo ''
echo -n "number of directories (including symbolic links) with $filetype files: "
find -L -type d -exec bash -c "ls -AF '{}'|grep -e '\.'${filetype}$ -e '\.'${filetype}'\*'$ > /dev/null && echo '{} contains file(s)'" \; 2>/dev/null |grep 'contains file(s)$'|wc -l

# count directories without 'filetype' files (good for checking; comment away after test)

echo -n "number of directories without $filetype files: "
find -type d -exec bash -c "ls -AF '{}'|grep -e '\.'${filetype}$ -e '\.'${filetype}'\*'$ > /dev/null || echo '{} empty'" \;|grep 'empty$'|wc -l

Тестовий вихід

З короткого шрифту:

$ ./ccntr 
29 7

З багатослівного оболонки:

$ LANG=C ./c-counter
number of c files in the current directory tree: 29
number of c symbolic links in the current directory tree: 1
number of c normal files in the current directory tree: 29
number of c symbolic links in the current directory tree including linked directories: 42
find: './cfiles/2/2': Too many levels of symbolic links
find: './cfiles/dirlink/2': Too many levels of symbolic links
---------- list directories:
. empty
./cfiles contains file(s)
./cfiles/2 contains file(s)
./cfiles/2/b contains file(s)
./cfiles/2/a contains file(s)
./cfiles/3 empty
./cfiles/3/b contains file(s)
./cfiles/3/a empty
./cfiles/1 contains file(s)
./cfiles/1/b empty
./cfiles/1/a empty
./cfiles/space d contains file(s)

number of directories with c files: 7
---------- list all directories including symbolic links:
. empty
./cfiles contains file(s)
./cfiles/2 contains file(s)
find: './cfiles/2/2': Too many levels of symbolic links
./cfiles/2/b contains file(s)
./cfiles/2/a contains file(s)
./cfiles/3 empty
./cfiles/3/b contains file(s)
./cfiles/3/a empty
./cfiles/dirlink empty
find: './cfiles/dirlink/2': Too many levels of symbolic links
./cfiles/dirlink/b contains file(s)
./cfiles/dirlink/a contains file(s)
./cfiles/1 contains file(s)
./cfiles/1/b empty
./cfiles/1/a empty
./cfiles/space d contains file(s)

number of directories (including symbolic links) with c files: 9
number of directories without c files: 5
$ 

4

Простий вкладиш Perl:

perl -MFile::Find=find -le'find(sub{/\.c\z/ and -f and $c{$File::Find::dir}=++$c}, @ARGV); print 0 + keys %c, " $c"' dir1 dir2

Або простіше за допомогою findкоманди:

find dir1 dir2 -type f -name '*.c' -printf '%h\0' | perl -l -0ne'$c{$_}=1}{print 0 + keys %c, " $."'

Якщо ви любите гольф і маєте недавній (наприклад, менше десяти років) Perl:

perl -MFile::Find=find -E'find(sub{/\.c$/&&-f&&($c{$File::Find::dir}=++$c)},".");say 0+keys%c," $c"'
find -type f -name '*.c' -printf '%h\0'|perl -0nE'$c{$_}=1}{say 0+keys%c," $."'

2

Подумайте про використання locateкоманди, яка набагато швидша за findкоманду.

Запуск на тестових даних

$ sudo updatedb # necessary if files in focus were added `cron` daily.
$ printf "Number Files: " && locate -0r "$PWD.*\.c$" | xargs -0 -I{} sh -c 'test ! -L "$1" && echo "regular file"' _  {} | wc -l &&  printf "Number Dirs.: " && locate -r "$PWD.*\.c$" | sed 's%/[^/]*$%/%' | uniq -cu | wc -l
Number Files: 29
Number Dirs.: 7

Дякую Муру за його відповідь, що він допомагає мені знімати символічні посилання з рахунку файлу Unix & Linux .

Дякую Тердону за його відповідь $PWD(не спрямована на мене) у відповіді Unix & Linux .


Оригінальна відповідь нижче, на яку посилаються коментарі

Коротка форма:

$ cd /
$ sudo updatedb
$ printf "Number Files: " && locate -cr "$PWD.*\.c$"
Number Files: 3523
$ printf "Number Dirs.: " && locate -r "$PWD.*\.c$" | sed 's%/[^/]*$%/%' | uniq -c | wc -l 
Number Dirs.: 648
  • sudo updatedbОновіть базу даних, яка використовується locateкомандою, якщо .cфайли створені сьогодні або якщо ви видалили.c файли сьогодні.
  • locate -cr "$PWD.*\.c$"знайдіть усі .cфайли в поточному каталозі, і це діти ( $PWD). Замість того, щоб друкувати імена файлів і друкувати кількість -cаргументів. В rвизначає регулярний вираз , а не по замовчуванням*pattern* відповідності , які можуть дати дуже багато результатів.
  • locate -r "$PWD.*\.c$" | sed 's%/[^/]*$%/%' | uniq -c | wc -l. Знайдіть усі *.cфайли в поточному каталозі та нижче. Видаліть ім’я файлу, sedзалишивши лише ім'я каталогу. Порахуйте кількість файлів у кожному каталозі, використовуючи uniq -c. Порахуйте кількість каталогів з wc -l.

Почніть в поточному каталозі з одного вкладиша

$ cd /usr/src
$ printf "Number Files: " && locate -cr "$PWD.*\.c$" &&  printf "Number Dirs.: " && locate -r "$PWD.*\.c$" | sed 's%/[^/]*$%/%' | uniq -c | wc -l
Number Files: 3430
Number Dirs.: 624

Зауважте, як змінилися кількість файлів та кількість директорій. Я вважаю, що всі користувачі мають /usr/srcкаталог і можуть працювати над командами з різними підрахунками залежно від кількості встановлених ядер.

Довга форма:

Довга форма включає час, щоб ви могли побачити, як швидше locateзакінчиться find. Навіть якщо вам доведеться запустити sudo updatedbце в багато разів швидше, ніж сингл find /.

───────────────────────────────────────────────────────────────────────────────────────────
rick@alien:~/Downloads$ sudo time updatedb
0.58user 1.32system 0:03.94elapsed 48%CPU (0avgtext+0avgdata 7568maxresident)k
48inputs+131920outputs (1major+3562minor)pagefaults 0swaps
───────────────────────────────────────────────────────────────────────────────────────────
rick@alien:~/Downloads$ time (printf "Number Files: " && locate -cr $PWD".*\.c$")
Number Files: 3523

real    0m0.775s
user    0m0.766s
sys     0m0.012s
───────────────────────────────────────────────────────────────────────────────────────────
rick@alien:~/Downloads$ time (printf "Number Dirs.: " && locate -r $PWD".*\.c$" | sed 's%/[^/]*$%/%' | uniq -c | wc -l) 
Number Dirs.: 648

real    0m0.778s
user    0m0.788s
sys     0m0.027s
───────────────────────────────────────────────────────────────────────────────────────────

Примітка. Це всі файли на ВСІХ дисках і розділах. тобто ми можемо також шукати команди Windows:

$ time (printf "Number Files: " && locate *.exe -c)
Number Files: 6541

real    0m0.946s
user    0m0.761s
sys     0m0.060s
───────────────────────────────────────────────────────────────────────────────────────────
rick@alien:~/Downloads$ time (printf "Number Dirs.: " && locate *.exe | sed 's%/[^/]*$%/%' | uniq -c | wc -l) 
Number Dirs.: 3394

real    0m0.942s
user    0m0.803s
sys     0m0.092s

У мене автоматично встановлені три розділи Windows 10 NTFS /etc/fstab . Будьте в курсі локації знає все!

Цікавий підрахунок:

$ time (printf "Number Files: " && locate / -c &&  printf "Number Dirs.: " && locate / | sed 's%/[^/]*$%/%' | uniq -c | wc -l)
Number Files: 1637135
Number Dirs.: 286705

real    0m15.460s
user    0m13.471s
sys     0m2.786s

Потрібно 15 секунд, щоб порахувати 1637,135 файлів у 286,705 каталогах. YMMV.

Щоб отримати детальну інформацію про locateобробку регулярних виразів команди (здається, вона не потрібна в цьому запитанні, але використовується для кожного випадку), будь ласка, прочитайте це: Використовуйте "Знайти" в якомусь конкретному каталозі?

Додаткове читання з останніх статей:


1
Це не враховує файли в конкретному каталозі. Як ви зазначаєте, він підраховує всі файли (або каталоги, або будь-який інший тип файлів), які відповідають .c(зауважте, що він порушиться, якщо -.cв поточному каталозі є файл, названий так, як ви не цитуєте *.c), і тоді він буде друкувати всі каталоги в системі, незалежно від того, чи містять вони файли .c
тердон

@terdon Ви можете передати каталог ~/my_c_progs/*.c. Він нараховує 638 каталогів з .cпрограмами, загальні каталоги відображаються пізніше 286,705. Я перегляну відповідь на подвійну цитату `" * .c ". Дякую за пораду.
WinEunuuchs2Unix

3
Так, ви можете використовувати щось подібне locate -r "/path/to/dir/.*\.c$", але це ніде у вашій відповіді не згадується. Ви посилаєтесь лише на іншу відповідь, в якій згадується це, але не пояснюючи, як його адаптувати, щоб відповісти на запитання, яке тут задають. Вся ваша відповідь орієнтована на те, як підрахувати загальну кількість файлів і каталогів у системі, що не відповідає питанню, яке було "як я можу підрахувати кількість .c файлів та кількість каталогів, що містять. c файли у певному каталозі ". Також ваші номери неправильні, спробуйте це на прикладі в ОП.
тердон

@terdon Дякую за ваш внесок. Я вдосконалив відповідь із вашими пропозиціями та відповіддю, яку ви опублікували на іншому веб-сайті для $PWDзмінної: unix.stackexchange.com/a/188191/200094
WinEunuuchs2Unix

1
Тепер ви повинні переконатися, що $PWDне містять символів, які, можливо, є особливими в регулярному
виразі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.