Модуль I · IT Skills (Hardware & Multimedia) · тема 6, урок 2 з 2 · 120 хвилин
Мета. Навчитися обробляти цілу теку файлів однією командою. Таку роботу називають пакетною обробкою. І записувати результат у файл, який читає програма, а не тільки людина. Учора ти визначав тип десяти файлів руками. Сьогодні це робить один скрипт — і робить однаково щоразу.
manifest.json, у якому числа не суперечать
одне одному;Слова case, set -e і JSON поки нічого
не означають — розберемо їх по черзі.
detect.sh, convert.sh,
manifest.json і index.html.media/lesson-06/ у себе і в
~/www/lesson-06/ на сервері. Не шукай lesson-12.| Етап | Хв | Що робимо |
|---|---|---|
| Перевірка ДЗ і розминка | 10 | Очікувані сигнатури проти реальних |
| Від ручного списку до скрипта | 10 | Цикл, підстановка імен, case |
Практика 1: detect.sh | 25 | Пишемо й запускаємо у своєму терміналі |
| Перерва | 10 | — |
Практика 2: convert.sh | 30 | Три гілки, ідемпотентність, два запуски |
| Практика 3: маніфест і сторінка | 15 | manifest.json, таблиця через fetch, FTP |
check 06 і виправлення | 10 | Доводимо автоперевірку до зеленого |
| Підсумок і ДЗ | 10 | Втратне й невтратне, домашнє завдання |
| Разом | 120 |
check 06 робить
статичний розбір: перевіряє синтаксис скриптів через bash -n,
шукає зашиті абсолютні шляхи й маркер ідемпотентності, а далі звіряє
manifest.json сам із собою. Що скрипт справді працює, ти доводиш
очно: вчитель на твоїй машині видаляє теку out/ і запускає
./convert.sh при тобі — двічі.Скрипт — це файл, у якому записані команди. Запускаєш файл — виконуються всі.
Учора ти перевірив 10 файлів руками і склав detect.txt. Це зайняло
хвилин п'ятнадцять. Тепер уяви 300 фото з телефона. Це вже не п'ятнадцять хвилин,
а година кліків. І десь на 180-му файлі ти помилишся й не помітиш.
Скрипт розв'язує не проблему швидкості. Він розв'язує проблему відтворюваності. Той самий вхід дає той самий вихід — сьогодні, завтра і на чужому комп'ютері. Тому вчитель зможе запустити твій скрипт при тобі й побачити те саме.
| Рядок | Що він робить |
|---|---|
#!/usr/bin/env bash | Шебанг. Перші два символи файлу кажуть системі, якою програмою його виконувати. Без нього ./hello.sh може піти не тим інтерпретатором |
chmod +x | ставить файлу право «виконуваний». Без нього буде Permission denied |
./hello.sh | крапка зі слешем — «файл із поточної теки». Без них оболонка шукає команду тільки в PATH |
${1:-.} | перший аргумент, а якщо його не дали — крапка, тобто поточна тека |
Ти зберіг скрипт як convert.sh і зробив chmod +x.
Набрав convert.sh . — а термінал відповідає
command not found. Файл на місці, права стоять. Що не так?
| № | Частина | Що робить | Типова помилка |
|---|---|---|---|
| 1 | for … in | ключове слово: почати цикл | забути done — оболонка мовчки чекає продовження |
| 2 | f | змінна кроку: сюди по черзі лягає повний шлях кожного файлу, а не саме ім'я | чекати в $f голе ім'я — для цього є basename |
| 3 | "$DIR"/* | список: оболонка розкриває * у перелік імен | писати $DIR/* без лапок або $(ls) — ламається на пробілах |
| 4 | do … done | межі тіла циклу | писати do одразу після списку без ; перед ним |
* нікуди не розкривається: цикл один раз отримає
буквальний рядок media/lesson-06/*. Тому перший рядок у тілі завжди
[ -f "$f" ] || continue — «не файл, іди далі».Оболонка вміє різати рядки без жодних зовнішніх програм. Ось увесь набір,
що знадобиться сьогодні. Приклад — файл media/my.photo.2024.jpg.
| Запис | Що робить | Результат |
|---|---|---|
${f##*.} | зрізати найдовше з початку до крапки — лишиться розширення | jpg |
${f#*.} | зрізати найкоротше з початку | photo.2024.jpg |
${f%.*} | зрізати найкоротше з кінця — лишиться шлях без розширення | media/my.photo.2024 |
${f%%.*} | зрізати найдовше з кінця | media/my |
$(basename "$f") | лише ім'я, без теки | my.photo.2024.jpg |
$(basename "${f%.*}") | «стебло»: ані теки, ані розширення | my.photo.2024 |
$(dirname "$f") | лише тека | media |
Правило запам'ятовується так: решітка ріже спереду, відсоток — ззаду.
На клавіатурі # лівіше за %. Подвоєний символ
жадібніший за одинарний.
Файл зветься my.photo.2024.jpg. Ти пишеш
mv "$f" "${f%%.*}.webp" і отримуєш my.webp —
дата й слово «photo» зникли. Що замінити?
У теці цілком може лежати літній табір 2024.jpg. Без лапок
оболонка розіб'є це ім'я на три окремі аргументи. Команда отримає три
неіснуючі файли.
Подвійний дефіс -- каже команді: далі йдуть імена файлів, а не
ключі. Він рятує, коли файл починається з дефіса. Без нього
-report.txt сприймається як невідомий ключ.
У теці є файл літній табір.jpg. Скрипт із рядком
mv $f ${f%.*}.webp обробляє решту файлів нормально. А на цьому
щоразу лається. Чому саме на ньому?
case: три гілки конвертаціїОдин цикл, різні дії для різних типів. Тип беремо не з розширення —
воно бреше — а з file --mime-type.
Учора ти встановив: розширення — це підказка, а не факт. Справжній тип видає
сигнатура в перших байтах, і file читає саме її. Сьогодні цей самий
MIME-рядок вирішує, що з файлом робити далі.
Три гілки — три різні долі файлу. Але є питання, спільне для всіх трьох: чи повернеться файл таким, як був. На уроці 6 ми розбирали це на звуці, тепер те саме стосується картинок і тексту.
Конвертація без втрат зберігає весь зміст до останнього символу: з результату завжди можна дістати оригінал назад. Конвертація з втратами частину даних викидає, щоб файл важив менше, і взяти їх назад уже нізвідки. Остання колонка таблиці каже, що саме відбувається в кожній гілці.
| Гілка | Чим конвертуємо | Що на виході | Втратна? |
|---|---|---|---|
image/*image/jpeg, image/png |
magick "$cur" -quality 82 "$dst" |
.webp |
так — WebP з якістю 82 викидає частину даних |
audio/*audio/mpeg |
ffmpeg -i "$cur" -c:a libmp3lame -b:a 192k "$dst" |
.mp3 192 кбіт/с |
так, і двічі — MP3 уже було стиснуто, тепер кодуємо ще раз |
text/*text/plain, text/markdown |
обгортка навколо тексту через printf і sed |
.html |
ні — жоден символ не втрачено, файл навіть побільшав |
<!doctype>, <title>
і <pre> — це десятки зайвих байтів. У маніфесті
saved_percent вийде від'ємним, і це правильно: економія мінус
дванадцять відсотків означає, що файл виріс на 12%. Не «виправляй» це нулем —
чекер перерахує відсоток сам і побачить розбіжність.*) має бути останньою
case бере першу гілку, що збіглася, і на цьому зупиняється.
Шаблон *) збігається геть з усім, тому поставлена першою, вона з'їсть
усі файли, а справжні гілки не спрацюють жодного разу.Скрипт відпрацьовує без жодної помилки й повертає код 0. Але тека
out/ лишається порожньою, а в терміналі десять рядків
«гілки немає». case виглядає так:
*) … ;; image/*) … ;; audio/*) … ;;. Де помилка?
text/plain — це і .txt, і .mdКоманда file --mime-type дивиться на вміст, а всередині
Markdown — це звичайний текст. Тому readme.md майже завжди буде
text/plain, а не text/markdown. Гілка в них спільна,
і це нормально. Обидва стають HTML, а імена вихідних файлів різні:
readme.html і notes.html.
Практичний наслідок для detect.sh: для text/*
не чіпай розширення. Інакше скрипт перейменує readme.md
на readme.txt і зіпсує те, що не було зламане.
За замовчуванням bash — оптиміст: команда впала, а скрипт іде далі й псує дані наступним кроком.
Уяви: magick не встановлено. Без захисту скрипт спокійно піде далі.
Запише в маніфест out_bytes: 0 і порахує економію 100%. А ти дізнаєшся
про це аж від чекера. Один рядок на початку це вимикає.
| Літера | Що вмикає | Що ловить сьогодні |
|---|---|---|
-e | перша ж команда з ненульовим кодом зупиняє скрипт | magick не встановлено — падаємо одразу, а не пишемо порожні файли |
-u | звертання до незаданої змінної — помилка | описався $OUTDIR замість $OUT — не запишеш файл у корінь диска |
-o pipefail | конвеєр вважається невдалим, якщо впала будь-яка його частина | file … | grep …: без цього код виходу дає лише grep |
-e, про який треба знати
Рядок на кшталт grep -c щось file нічого не знайшов — це код 1,
і скрипт із -e помре. Якщо невдача очікувана, гаси її явно:
grep -c щось file || true. Це не милиця, а спосіб сказати
«тут нуль результатів — нормально».| Перевірка | Читається як | Навіщо |
|---|---|---|
[ -f "$f" ] | це звичайний файл | відсіяти теки й нерозкриту зірочку |
[ -d "$DIR" ] | це тека | сказати «немає теки», а не сипати помилками 40 рядків |
[ -e "$t" ] | щось із таким ім'ям уже існує | не затерти чужий файл під час перейменування |
[ -n "$s" ] / [ -z "$s" ] | рядок непорожній / порожній | пропустити порожні рядки у звіті |
[ "$a" -nt "$b" ] | файл a новіший за b | серце ідемпотентності: чи треба конвертувати заново |
command -v magick | така команда взагалі є в системі | чесне «встанови ImageMagick» замість command not found ×10 |
Кожен скрипт після себе лишає число. 0 — усе добре, будь-що інше — біда.
Його не видно на екрані. Але його бачить чекер, і за ним же працює
&&. Перевірити свій код виходу можна одразу після запуску.
* і кладе результат у ту саму теку.
Сам цикл не зациклиться — оболонка розкрила зірочку один раз. Але
наступний запуск побачить уже й твої .webp, і зробить із них
.webp.webp. Третій — .webp.webp.webp. Тека росте
до заповнення диска. Ліки — окрема тека out/ і перевірка «вже готово».Ти запустив скрипт двічі, і в теці з'явилися photo.webp
і photo.webp.webp. ImageMagick цілий, синтаксис без помилок.
Де помилка?
Запустив двічі — результат той самий. Запустив десять разів — той самий. Ідея коротка, слово для неї довге.
Операція ідемпотентна, якщо повторення нічого не змінює. Натиснути кнопку
ліфта вдруге — ідемпотентно. Додати ложку цукру вдруге — ні. Твій
convert.sh має бути кнопкою ліфта.
out/ і запустить скрипт при тобі, потім
запустить його ще раз і подивиться, що нічого не змінилося.| Маркер | Що перевіряє | Коли підводить |
|---|---|---|
[ -f "$dst" ] && continue | вихідний файл узагалі існує | ти виправив оригінал — результат лишиться старим |
[ "$dst" -nt "$cur" ] | вихід новіший за вхід | майже не підводить; саме це ми й беремо |
sha256sum вхідного файлу в окремому файлі | вміст не змінився | найнадійніше, але треба зберігати хеші й читати їх — зайва складність для 10 файлів |
Для detect.sh маркер інший: дописуй лише те, чого ще немає.
Файл detect.tsv не переписується з нуля, а доповнюється. Перед
дописом скрипт питає, чи це ім'я вже в ньому є. Це важливо. Після перейменування
оригінальне ім'я існує тільки в цьому файлі — на диску його вже нема.
Ти поставив у скрипт перевірку [ -f "$dst" ] && continue.
Потім обрізав photo.jpg у редакторі й перезапустив скрипт.
А out/photo.webp лишився старим, з необрізаною картинкою.
Що змінити в перевірці?
Тека на 8 файлів. Три з них брешуть розширенням. Тисни кнопки по черзі й дивись, що відбувається з кожним файлом.
Спочатку ./detect.sh. Він читає перші байти, називає справжній тип
і виправляє хибні розширення. Потім ./convert.sh. Він розкладає файли
по трьох гілках і збирає маніфест. Потім запусти ./convert.sh
ще раз і подивись, що змінилося.
У симуляторі 8 файлів, а у твоєму наборі їх 10 — тут просто менше, щоб усе вміщалося на екран. Числа ілюстративні: вони підібрані так, щоб було видно арифметику економії. На своїй машині ти отримаєш власні, і саме їх запише скрипт.
diagram.txt усередині — PNG. Після перейменування він потрапляє
не в текстову гілку, а в графічну. Перейменування змінює долю файлу.notes.txt після конвертації побільшав, і економія від'ємна.
Це не помилка, це обгортка HTML../convert.sh не створює жодного файлу, а маніфест
виходить той самий. Це і є ідемпотентність../convert.sh першим — скрипт відмовиться працювати.
Він не вгадує тип сам, він читає detect.tsv.У однокласника після другого запуску convert.sh у маніфесті стало
20 об'єктів замість 10. Жодного нового файлу при цьому не створилося. Чекер
вимагає рівно 10. Що він зробив не так у скрипті?
Це робочі скрипти, а не уривки. Набирай їх руками — так конструкції запам'ятовуються. Розбір кожного рядка — у таблиці під кодом.
detect.sh| Рядки | Що там відбувається |
|---|---|
| 1 | шебанг: система знає, чим виконувати файл |
| 4 | вмикаємо чесні падіння: -e, -u, pipefail |
| 6 | ключовий рядок для чекера. Тека береться з аргументу. Жодного cd /home/… — скрипт працює з будь-якою текою |
| 8–9 | тека є? звіт існує? Якщо ні — створюємо порожній, але не очищаємо наявний |
| 11–12 | цикл по всьому, що в теці; усе, що не файл, пропускаємо |
| 13–14 | беремо голе ім'я і виключаємо власні службові файли, щоб скрипт не обробляв сам себе |
| 16–19 | маркер ідемпотентності. awk шукає це ім'я і в першій колонці (як було), і в третій (як стало). Знайшов — пропускаємо. Тому повторний запуск не дублює рядків |
| 21–22 | розширення і стебло — та сама підстановка з кроку 2 |
| 23 | справжній тип. -b прибирає ім'я файлу з виводу, лишається чистий MIME |
| 25–29 | яким має бути розширення для цього типу. Для всього іншого — *) — лишаємо як є: текст ми не чіпаємо |
| 32–41 | перейменування, але тільки якщо ціль ще не зайнята. Інакше можна затерти чужий файл — і без попередження |
| 43 | рядок звіту. У першій колонці — оригінальне ім'я: на диску його вже немає, і це єдине місце, де воно збереглося |
| 46 | підсумок для людини. Код виходу при цьому 0 — чекер бачить успіх |
convert.sh| Рядки | Що там відбувається |
|---|---|
| 6–9 | усі шляхи — від $DIR. Змінив аргумент — скрипт працює в іншій теці й нічого не треба правити всередині |
| 11 | без detect.tsv робити нічого: convert.sh не вгадує типи сам |
| 12–13 | чесна відмова, якщо інструментів немає. Інакше set -e обірве скрипт на першому файлі з незрозумілим текстом |
| 16–18 | маніфест збирається у тимчасовий файл. Так на диску ніколи не лежить наполовину записаний JSON |
| 20 | IFS=$'\t' — розділяти рядок саме табуляцією, тому імена з пробілами не розваляться. -r — не тлумачити зворотні слеші |
| 22 | ${renamed:-$src} — «нове ім'я, а якщо його немає, то старе». Один запис замість if |
| 26–30 | вибір гілки за MIME, а не за розширенням. Розширення вже виправлене, але вирішує все одно тип |
| 33 | серце ідемпотентності: результат є і новіший за джерело — пропускаємо. Оригінал змінився — переробимо |
| 36–42 | три гілки. Текстова не викликає нічого зовнішнього: printf дає обгортку, sed екранує &, <, >, щоб код усередині тексту не поламав HTML |
| 47–48 | розміри читаємо з диска, а не з голови. tr -d ' ' прибирає пробіли, які додає wc на macOS |
| 49 | економію рахує awk з одним знаком після коми. Ти її не вводиш руками — саме цього вимагає чекер |
| 51–54 | кома ставиться перед наступним об'єктом, а не після попереднього. Так у JSON не з'явиться зайва кома в кінці — найчастіша причина «invalid JSON» |
| 55 | ось звідки цикл читає рядки: done < "$TSV". Файл підключається до циклу цілком, один раз |
| 57–58 | закриваємо масив і одним рухом замінюємо старий маніфест новим. Якщо скрипт упаде раніше — старий файл лишиться цілим |
ffmpeg з'їдає список
Цикл while read читає файл зі свого стандартного входу. ffmpeg
теж читає стандартний вхід — і мовчки проковтує решту detect.tsv.
Симптом: скрипт обробив один файл і закінчився без помилки. Ліки — ключ
-nostdin у рядку 38 (або < /dev/null після команди).magick чи convert?
В ImageMagick 7 команда зветься magick, у старій шостій версії —
convert. Перевір своєю magick -version. На Windows
convert ще й збігається з системною командою форматування диска,
тому magick — єдиний безпечний варіант.Твій скрипт починається рядком cd /home/petrenko/media/lesson-06
і далі працює з *. У тебе все зелено. На очній перевірці вчитель
копіює скрипт у свою тестову теку й запускає. Скрипт мовчки обробляє
твої файли. Що написати замість цього рядка?
Якщо в тебе Windows і немає Git Bash чи WSL — працюй цими. Логіка та сама, слова інші.
Одна принципова відмінність: у PowerShell немає команди file.
Тому справжній тип ми визначаємо самі. Читаємо перші чотири байти й порівнюємо
з сигнатурами. З тими самими, що ти записав на минулому уроці.
| bash | PowerShell | Про що це |
|---|---|---|
set -e | $ErrorActionPreference = "Stop" | падати на першій помилці |
DIR="${1:-.}" | param([string]$Dir = ".") | тека з аргументу |
for f in "$DIR"/* | foreach ($f in Get-ChildItem $Dir -File) | цикл по файлах |
case … in … esac | if/elseif з -like | розгалуження за MIME |
${f%.*} | [IO.Path]::GetFileNameWithoutExtension($f) | відрізати розширення |
mv -- a b | Rename-Item -LiteralPath a -NewName b | перейменувати |
wc -c < f | (Get-Item f).Length | розмір у байтах |
[ a -nt b ] | (Get-Item a).LastWriteTime -ge (Get-Item b).LastWriteTime | чи свіжий результат |
echo $? | $LASTEXITCODE | код виходу |
detect.ps1| Рядки | Що там відбувається |
|---|---|
| 3 | param має стояти першою виконуваною інструкцією файлу — коментарі перед ним можна, код ні |
| 10–16 | той самий маркер ідемпотентності: збираємо всі вже описані імена — і в колонці «було», і в колонці «стало» |
| 18–26 | власний file: чотири байти → hex → сигнатура. FFD8FF — JPEG, 89504E47 — PNG, 494433 — мітка ID3 на початку MP3 |
| 19 | -Encoding Byte працює у PowerShell 5.1. У PowerShell 7 цей ключ прибрали — там пиши -AsByteStream |
| 35 | ім'я запам'ятовується до перейменування. Після Rename-Item об'єкт $f уже може вказувати на нове ім'я |
| 50 | `t — це табуляція. У PowerShell символ утечі не зворотний слеш, а зворотний апостроф |
convert.ps1| Граблі | Симптом | Що робити |
|---|---|---|
> пише UTF-16 | файл на вигляд правильний, а jq каже, що це не JSON | ніколи не … > manifest.json; тільки Out-File -Encoding utf8, а для JSON — [IO.File]::WriteAllText |
Out-File -Encoding utf8 у 5.1 додає BOM | jq: error: Invalid numeric literal у першому ж символі | писати через [IO.File]::WriteAllText(… , (New-Object Text.UTF8Encoding $false)) |
немає && | mkdir out && magick … — помилка розбору | mkdir out; if ($?) { magick … } або просто окремими рядками |
ConvertTo-Json на одному елементі | у файлі {…} замість [{…}], чекер каже «не масив» | збирати масив рядком, як у 62-му рядку, або -AsArray у PowerShell 7 |
continue всередині switch
У PowerShell continue у блоці switch виходить із самого
switch, а не з циклу навколо. Через це половина файлів мовчки
пропускається. Саме тому в цьому скрипті замість switch —
звичайні if/elseif.Скрипт на PowerShell 5.1 записав manifest.json через
Out-File -Encoding utf8. У блокноті файл виглядає бездоганно,
браузер його показує. А jq empty manifest.json відповідає
Invalid numeric literal at line 1. Що з файлом?
manifest.json — звіт, який читає машинаТой самий список, що вчора був текстом для людини. Тепер його вміє прочитати і браузер, і чекер, і ти сам через рік.
JSON — це звичайний текст, який уміє читати програма. Правил у ньому мало, зате всі суворі. Подивимось на них на прикладі свого ж маніфесту.
Опис одного файлу беруть у фігурні дужки. Усередині — пари «ключ: значення», як у картці бібліотечного каталогу. Така картка зветься об'єктом. Десять карток поспіль складають у квадратні дужки — це вже масив, тобто просто список.
Значення в картці бувають двох головних видів. Текст беруть у подвійні
лапки: "image/jpeg". Число пишуть без лапок узагалі:
2418176. Є ще true і false для «так»
і «ні» та null для «значення немає».
Трьох речей JSON не пробачає. Одинарні лапки замість подвійних він вважає помилкою. Кома після останнього елемента ламає файл так само. А коментар усередину не вставити ніяк — у JSON їх немає.
| Ключ | Тип | Що містить |
|---|---|---|
src | рядок | ім'я файлу до будь-якого перейменування |
src_bytes | число | розмір оригіналу в байтах, без лапок |
detected_mime | рядок | справжній тип, як його назвав file --mime-type |
renamed_to | рядок | нове ім'я або порожній рядок, якщо розширення було правдиве |
out | рядок | шлях результату відносно теки: out/… |
out_bytes | число | розмір результату в байтах |
saved_percent | число | (1 − out_bytes ÷ src_bytes) × 100, один знак після коми; може бути від'ємним |
[
{"src":"sunset.jpg","src_bytes":2418176,"detected_mime":"image/jpeg","renamed_to":"","out":"out/sunset.webp","out_bytes":512044,"saved_percent":78.8},
{"src":"notes.txt","src_bytes":3214,"detected_mime":"text/plain","renamed_to":"","out":"out/notes.html","out_bytes":3620,"saved_percent":-12.6},
{"src":"photo.mp3","src_bytes":1887436,"detected_mime":"image/jpeg","renamed_to":"photo.jpg","out":"out/photo.webp","out_bytes":402190,"saved_percent":78.7}
]
Числа тут — ілюстративні, ті самі, що в симуляторі вище. У твоєму маніфесті будуть власні: їх запише скрипт із реальних файлів.
Прочитай третій рядок уважно. Файл звався photo.mp3, а всередині
виявився JPEG. Скрипт перейменував його на photo.jpg, і результат
пішов у графічну гілку — photo.webp. Одне поле
renamed_to пояснює всю історію файлу.
Найцікавіша перевірка — п'ята. Чекер не вірить полю saved_percent.
Він рахує відсоток сам із двох інших полів і порівнює з твоїм. Якщо розбіжність
більша за 0,5 — цей критерій поки не зарахують. Прожени цю перевірку сам, перш
ніж здавати: так виправляти буде швидше.
"src_bytes": "2418176" — це рядок, а не число.
jq empty промовчить: з погляду синтаксису все гаразд. А перерахунок
економії впаде або дасть дурницю, бо ділити текст на текст не можна. У маніфесті
число має бути число.У твоєму маніфесті "src_bytes": "2418176" і
"out_bytes": "512044" — у лапках. jq empty мовчить,
jq length дає 10. Усі сім ключів на місці. Чому чекер усе одно
валить критерій «несуперечливість»?
Десять рядків таблиці можна набити в index.html руками. Тоді
після кожного запуску скрипта їх доведеться правити руками ж. Зробимо інакше:
у файлі лишається порожня таблиця, а рядки в неї дописує сама сторінка,
коли її відкривають.
Робить це маленький скрипт унизу сторінки. Спершу він просить у сервера файл
manifest.json. Такий запит і зветься fetch —
англійською «принеси». Відповідь приходить не миттєво, тому далі стоїть
.then: «а коли принесеш — зроби ось це».
Принесений текст перетворюють на список карток — це r.json().
Порожню таблицю скрипт знаходить за її іменем #t, і це
querySelector. Далі він іде по списку й на кожну картку додає
в таблицю новий рядок.
Рядок роблять із трьох дій. createElement('tr') створює порожній
рядок, textContent кладе в клітинку значення,
appendChild чіпляє готове до таблиці. Отак десять карток стають
десятьма рядками на екрані.
Коли сторінка будує себе з даних просто в браузері, це називають
рендером. Саме тому чекер рахує рядки двічі. У самому файлі
index.html їх менше трьох, а після рендера — одинадцять: заголовок
і десять файлів. Це й доводить, що таблицю справді малює fetch,
а не твої руки.
textContent, а не innerHTML
Імена файлів приходять із маніфесту. Якщо хтось назве файл
<script>…, innerHTML виконає це як код сторінки.
textContent покаже його як текст. Звичка, яка колись урятує тобі сайт.Скрипти пишуться й запускаються на твоєму комп'ютері. На сервер поїдуть лише чотири текстові файли. Галочки зберігаються — сторінку можна закрити.
media/lesson-06/ — набір, out/,
detect.tsv, обидва скрипти. По FTP у ~/www/lesson-06/:
detect.sh, convert.sh, manifest.json,
index.html. Ані набору, ані теки out/ на сервері бути
не повинно — критерій no_media це перевіряє.check 06Перевірка статична: конвертація на сервері не запускається жодного разу. Спроби не обмежені.
bash -n, шукає в них
зашиті абсолютні шляхи і маркер ідемпотентності, звіряє
manifest.json сам із собою і з еталонним списком набору, який знає
наперед. Він не бачить ані твоїх WebP, ані MP3 і не може сказати,
чи скрипт справді працює. Це доводиться очно.Демонстраційний режим: результат згенеровано для показу. На сервері
ця кнопка викликає POST /api/check, який запускає чекер уроку
від імені учня.
out/ і запускає ./convert.sh при тобі —
десять файлів створюються заново;manifest.json;Це те, що бачить учитель, коли виставляє оцінку.
| Складник | Вага | Результат |
|---|
file, тому підроблене розширення скрипт
не обдурить.| Ланцюжок | Втрати | Чому |
|---|---|---|
| PNG → PNG | немає | PNG стискає без втрат: розпакував — отримав ті самі пікселі |
| TXT → HTML | немає | жоден символ не зник, файл лише побільшав на розмітку |
| FLAC → WAV | немає | FLAC — невтратний архів звуку, WAV — той самий звук без стиснення |
| JPEG → WebP q82 | є | друге втратне кодування поверх першого |
| MP3 → WAV → MP3 | є, двічі | WAV посередині нічого не повертає: викинуте на першому кроці вже не існує |
Головне правило: оригінал не видаляй. Втратна конвертація незворотна, і жодна кількість проміжних невтратних форматів її не скасує.
Унизу твоєї сторінки є абзац для висновку — у коді він позначений іменем
conclusion. Напиши в ньому, який ланцюжок конвертацій ти обрав би
для власних фото і чому саме такий порядок.
Обидва терміни — «з втратами» і «без втрат» — мають бути в тексті: їх окремо шукає чекер. Мінімум 200 символів.
Синтаксис — з офіційних довідників, а не з пам'яті. Перевіряти дозволено й корисно.
${1:-.}, ${f##*.}, ${f%.*},
${renamed:-$src}.
gnu.org/software/bash/manual-e, -u, -o pipefail.
gnu.org/software/bash/manualfor, while,
case і правило «перша гілка, що збіглася».
gnu.org/software/bash/manualtest — умови -f, -d,
-e, -n, -nt.
pubs.opengroup.orgfile(1) — визначення типу за сигнатурою, ключі
--mime-type і -b.
man7.org/linux/man-pages/man1/file.1.htmlmagick у 7-й версії,
ключ -quality.
imagemagick.org-i, -c:a,
-b:a, -y, -loglevel і
-nostdin — саме він рятує цикл while read.
ffmpeg.org/ffmpeg.htmlempty, length, keys,
select, round, @tsv.
jqlang.github.io/jq/manualfetch() і MDN — Node.textContent:
завантаження JSON і безпечне вставляння тексту.
developer.mozilla.org/docs/Web/API/Window/fetchimage/jpeg,
image/png, audio/mpeg, text/plain.
iana.org/assignments/media-typesabout_Redirection: чому >
у Windows PowerShell 5.1 пише UTF-16.
learn.microsoft.comOut-File і ConvertTo-Json:
поведінка -Encoding utf8 у 5.1 і -AsArray у 7.
learn.microsoft.com/…/out-fileGet-Content: -Encoding Byte
у 5.1, -AsByteStream у 7.
learn.microsoft.com/…/get-contentПовний список із поясненнями, що саме звідки взято, — у файлі
urok-12-джерела.md поруч із цією сторінкою.