Написание скриптов на Bash/CMD/PowerShell

Рейтинг: 61% · 6 голосов
Полный курс по Android Debug Bridge: установка, подключение, shell, логи, dumpsys, автоматизация, root, беспроводная отладка. Уроки по главам с обсуждением.
Ответить
Аватара пользователя
android_roman
Сообщения: 45
Зарегистрирован: 11 май 2026, 05:31

Написание скриптов на Bash/CMD/PowerShell

Сообщение android_roman »

АкадемияADB: Android Debug BridgeГлава 22 из 29
Оглавление курса (29)
  1. Введение в Android Debug Bridge
  2. Установка и настройка рабочей среды
  3. Подключение устройства (проводное и беспроводное)
  4. Базовые команды ADB и управление сервером
  5. Команды состояния и перезагрузки
  6. Навигация по файловой системе
  7. Управление пакетами приложений
  8. Логирование с помощью logcat
  9. Системные дампы и диагностика (dumpsys)
  10. Анализ производительности в реальном времени
  11. Эмуляция ввода (input)
  12. Управление Activity и Intent (am)
  13. Работа с оконным менеджером (wm)
  14. Захват экрана и запись видео
  15. Root-доступ и его применение
  16. Модификация системных настроек через settings
  17. Команды для поставщиков контента (content)
  18. Резервное копирование и восстановление (backup)
  19. Проброс портов и туннелирование
  20. Беспроводная отладка (Wi-Fi)
  21. Взаимодействие с эмуляторами
  22. Написание скриптов на Bash/CMD/PowerShell (вы здесь)
  23. ADB в языках программирования
  24. Автоматизация тестирования с ADB
  25. Безопасность и лучшие практики
  26. ADB на Android TV, Wear OS и IoT
  27. Восстановление и низкоуровневые операции
  28. Расширенные возможности оболочки и инструменты
  29. Отладка самого ADB и устранение неполадок
К этой главе весь арсенал adb вы знаете по отдельности: pm из главы 7, logcat из главы 8, am из главы 12. Пора склеивать команды в скрипты, которые работают без присмотра: ставят сборку на десяток устройств, собирают логи по ночам, чистят приложение перед прогоном тестов. Эта глава про обвязку на Bash (Linux/macOS), CMD и PowerShell (Windows). В главе 23 поднимемся на уровень Python и Kotlin, но фундамент закладывается здесь.

Примеры рассчитаны на свежие Platform Tools, проверьте версию:

Код: Выделить всё

$ adb --version
Android Debug Bridge version 1.0.41
Version 37.0.0-14910828
Installed as /opt/platform-tools/adb
Почему наивные скрипты ломаются:

Три типичные причины. Первая: к машине подключено больше одного устройства, и любая команда без флага -s падает:

Код: Выделить всё

$ adb shell getprop ro.build.version.release
adb: more than one device/emulator
Вторая: до Android 7.0 adb shell не пробрасывал код возврата удалённой команды и всегда возвращал 0. Современный shell-протокол v2 (adbd с API 24 плюс adb 1.0.36 и новее) коды пробрасывает честно, но на старом парке (а в СНГ до сих пор живы терминалы сбора данных на Android 5/6) проверка $? молча врёт. Третья: на тех же старых устройствах строки из adb shell приходят с \r\n даже на Linux, и сравнение строк тихо ломается. Всё это лечится идиомами ниже.

Ожидание устройства:

Базовая команда блокируется, пока транспорт не перейдёт в состояние device:

Код: Выделить всё

adb wait-for-device
Полный синтаксис: wait-for[-TRANSPORT]-STATE, где TRANSPORT это usb, local или any (по умолчанию), а STATE это device, recovery, rescue, sideload, bootloader или disconnect. Полезные варианты:

Код: Выделить всё

adb wait-for-usb-device              # только физическое USB-устройство
adb -s R58M42ABCDE wait-for-device   # ждать конкретный серийник
adb wait-for-disconnect              # дождаться отключения
Главная ловушка: wait-for-device отпускает сразу, как только adbd поднялся, а это происходит задолго до конца загрузки Android. Если сразу дёрнуть pm install или am start, поймаете ошибки вида Can't find service: package. Правильная идиома, дождаться флага sys.boot_completed:

Код: Выделить всё

adb wait-for-device shell 'while [ "$(getprop sys.boot_completed)" != "1" ]; do sleep 1; done'
wait-for-device здесь работает как префикс: adb сначала ждёт устройство, потом выполняет shell. Вторая ловушка: устройство в состоянии unauthorized (RSA-ключ не принят) никогда не станет device, и скрипт зависнет навечно. Оборачивайте ожидание в таймаут:

Код: Выделить всё

timeout 120 adb wait-for-device || { echo "device not found" >&2; exit 1; }
В PowerShell таймаут делается через job:

Код: Выделить всё

$j = Start-Job { adb wait-for-device }
if (-not (Wait-Job $j -Timeout 120)) { Stop-Job $j; Write-Error "device not found"; exit 1 }
Цикл по устройствам:

Источник правды, вывод adb devices. Первая строка заголовок, дальше пары "серийник состояние". Берём только строки с состоянием device, а offline и unauthorized пропускаем:

Код: Выделить всё

$ adb devices
List of devices attached
R58M42ABCDE	device
emulator-5554	device
192.168.1.42:42715	unauthorized
Bash-идиома:

Код: Выделить всё

#!/usr/bin/env bash
set -euo pipefail

for serial in $(adb devices | awk 'NR>1 && $2=="device" {print $1}'); do
    echo "== $serial =="
    adb -s "$serial" shell getprop ro.product.model
done
Типичный вывод:

Код: Выделить всё

== R58M42ABCDE ==
SM-A546E
== emulator-5554 ==
sdk_gphone64_x86_64
Вместо -s в каждой команде можно один раз выставить переменную окружения, adb подхватит её сам:

Код: Выделить всё

export ANDROID_SERIAL=R58M42ABCDE
adb shell getprop ro.product.model    # уйдёт на R58M42ABCDE
CMD-вариант через for /f (skip=1 пропускает заголовок, в .bat проценты удваиваются, в интерактивной консоли пишите %a):

Код: Выделить всё

@echo off
for /f "skip=1 tokens=1,2" %%a in ('adb devices') do (
    if "%%b"=="device" (
        echo == %%a ==
        adb -s %%a shell getprop ro.product.model
    )
)
PowerShell, регулярка заодно отсекает заголовок и пустые строки:

Код: Выделить всё

$devices = adb devices | Select-String "^(\S+)\s+device$" |
    ForEach-Object { $_.Matches[0].Groups[1].Value }
foreach ($d in $devices) {
    adb -s $d shell getprop ro.product.model
}
Обработка ошибок:

Каждый Bash-скрипт начинайте со строгого режима:

Код: Выделить всё

set -euo pipefail
Сам adb возвращает ненулевой код, если не нашёл устройство или команда провалилась. С adb shell тоньше: код удалённой команды доезжает только при shell-протоколе v2 (Android 7.0+). Быстрая проверка вашей связки:

Код: Выделить всё

$ adb shell false; echo $?
1
Если тут печатается 0, устройство старое, используйте идиому с маркером:

Код: Выделить всё

out=$(adb shell 'pm clear com.example.app; echo RC=$?' | tr -d '\r')
case "$out" in *"RC=0"*) echo ok ;; *) echo "fail: $out" >&2; exit 1 ;; esac
Отдельный капкан: на части старых прошивок утилиты вроде pm печатали Failure в stdout и завершались нулём. Поэтому в скриптах под разношёрстный парк до сих пор парсят слово Success, это надёжнее кода возврата:

Код: Выделить всё

res=$(adb -s "$serial" install -r -g app-release.apk 2>&1)
if [[ "$res" == *"Success"* ]]; then
    echo "$serial: ok"
else
    echo "$serial: FAILED: $res" >&2
fi
В CMD проверяйте errorlevel сразу после команды:

Код: Выделить всё

adb install -r app-release.apk
if errorlevel 1 (
    echo install failed
    exit /b 1
)
В PowerShell код возврата нативной программы лежит в $LASTEXITCODE:

Код: Выделить всё

adb install -r app-release.apk
if ($LASTEXITCODE -ne 0) { Write-Error "install failed"; exit 1 }
Нюанс PowerShell 5.1: adb пишет служебные сообщения в stderr, и при $ErrorActionPreference = "Stop" они превращаются в исключения. При захвате вывода добавляйте 2>&1.

Парсинг вывода adb:

Правило первое: срезайте \r, если строка пойдёт в сравнение или в имя файла:

Код: Выделить всё

ver=$(adb shell getprop ro.build.version.release | tr -d '\r')
Правило второе: вытаскивайте машиночитаемые куски, а не парсите простыни целиком. Список сторонних пакетов без префикса:

Код: Выделить всё

$ adb shell pm list packages -3 | tr -d '\r' | sed 's/^package://'
com.example.app
org.telegram.messenger
ru.yandex.searchplugin
Версия приложения без полного дампа:

Код: Выделить всё

$ adb shell dumpsys package com.example.app | grep -m1 versionName
    versionName=3.14.1
PID процесса (понадобится для логов) и уровень батареи числом:

Код: Выделить всё

$ adb shell pidof -s com.example.app
12843
$ adb shell dumpsys battery | awk '/level:/ {print $2}'
87
Автоматизация: разворачивание приложения:

Скрипт деплоя: собрать дебаг-сборку, поставить, запустить главную Activity. Имя Activity не хардкодим, спрашиваем у системы (cmd package resolve-activity доступен на Android 7.0 и новее):

Код: Выделить всё

#!/usr/bin/env bash
set -euo pipefail
PKG=com.example.app
APK=app/build/outputs/apk/debug/app-debug.apk

./gradlew assembleDebug
adb wait-for-device
adb install -r -t -g "$APK"

ACT=$(adb shell cmd package resolve-activity --brief "$PKG" | tail -n 1 | tr -d '\r')
adb shell am start -n "$ACT"
Флаги install: -r переустановка с сохранением данных, -t разрешает testOnly-сборки, -g сразу выдаёт все runtime-разрешения из манифеста. Типичный вывод:

Код: Выделить всё

Performing Streamed Install
Success
Starting: Intent { cmp=com.example.app/.MainActivity }
Автоматизация: снятие логов:

Сценарий: воспроизвели баг, одним скриптом собрали комплект для тикета. Логи фильтруем по PID приложения (фильтр --pid работает с Android 7.0), чтобы не тащить чужой шум:

Код: Выделить всё

#!/usr/bin/env bash
set -euo pipefail
PKG=${1:?usage: grab_logs.sh <package>}
DIR=logs_$(date +%Y%m%d_%H%M%S)
mkdir -p "$DIR"

PID=$(adb shell pidof -s "$PKG" | tr -d '\r' || true)
if [[ -n "$PID" ]]; then
    adb logcat -d -v threadtime --pid="$PID" > "$DIR/app.log"
fi
adb logcat -d -v threadtime -b crash > "$DIR/crash.log"
adb shell dumpsys meminfo "$PKG" > "$DIR/meminfo.txt"
adb exec-out screencap -p > "$DIR/screen.png"
echo "saved to $DIR"
Флаг -d отдаёт накопленный буфер и сразу выходит, скрипт не виснет на потоке. Перед воспроизведением бага очищайте буфер командой adb logcat -c, тогда в app.log не будет вчерашней истории.

Пример: полный сброс приложения:

Состояние "как после чистой установки" без переустановки APK. pm clear сносит данные, кэш и отзывает runtime-разрешения, поэтому для автотестов нужные разрешения возвращаем сразу, чтобы не упереться в системные диалоги:

Код: Выделить всё

#!/usr/bin/env bash
set -euo pipefail
PKG=${1:?usage: app_reset.sh <package> [serial]}
SERIAL=${2:-}
ADB=(adb); [[ -n "$SERIAL" ]] && ADB=(adb -s "$SERIAL")

"${ADB[@]}" shell am force-stop "$PKG"
out=$("${ADB[@]}" shell pm clear "$PKG" | tr -d '\r')
[[ "$out" == "Success" ]] || { echo "pm clear failed: $out" >&2; exit 1; }

"${ADB[@]}" shell pm grant "$PKG" android.permission.POST_NOTIFICATIONS
"${ADB[@]}" shell pm grant "$PKG" android.permission.ACCESS_FINE_LOCATION
echo "reset done: $PKG"
Уточнения. pm clear сам убивает процесс, force-stop тут для предсказуемости на прошивках с агрессивным автоперезапуском. pm grant работает только для разрешений, объявленных в манифесте приложения, иначе получите SecurityException. POST_NOTIFICATIONS существует с Android 13.

Пример: батч-установка APK:

Задача из жизни тестовой лаборатории или сервис-центра: каталог с APK, парк устройств, поставить всё на всех и получить сводку. Здесь сознательно нет set -e, ошибки обрабатываем сами и не роняем весь прогон из-за одного файла:

Код: Выделить всё

#!/usr/bin/env bash
set -uo pipefail
shopt -s nullglob
APKS=(apk/*.apk)
[[ ${#APKS[@]} -gt 0 ]] || { echo "no apk found" >&2; exit 1; }

fail=0
for serial in $(adb devices | awk 'NR>1 && $2=="device" {print $1}'); do
    for apk in "${APKS[@]}"; do
        if out=$(adb -s "$serial" install -r -g "$apk" 2>&1) && [[ "$out" == *Success* ]]; then
            echo "[$serial] $(basename "$apk"): OK"
        else
            echo "[$serial] $(basename "$apk"): FAIL ($out)" >&2
            fail=1
        fi
    done
done
exit $fail
Типичный вывод:

Код: Выделить всё

[R58M42ABCDE] telegram-11.9.0.apk: OK
[R58M42ABCDE] 2gis-6.48.apk: OK
[emulator-5554] telegram-11.9.0.apk: FAIL (adb: failed to install apk/telegram-11.9.0.apk: Failure [INSTALL_FAILED_NO_MATCHING_ABIS])
Не путайте два смежных инструмента. install-multiple ставит набор split-APK одного приложения (base плюс сплиты из app bundle). install-multi-package ставит несколько разных пакетов одной атомарной сессией и требует Android 10+:

Код: Выделить всё

adb install-multiple base.apk split_config.arm64_v8a.apk split_config.ru.apk
adb install-multi-package app1.apk app2.apk app3.apk
CMD-версия батча на одно устройство:

Код: Выделить всё

@echo off
setlocal
set FAIL=0
for %%f in (apk\*.apk) do (
    adb install -r -g "%%f" >nul 2>&1
    if errorlevel 1 (
        echo %%f: FAIL
        set FAIL=1
    ) else (
        echo %%f: OK
    )
)
exit /b %FAIL%
Частые ошибки напоследок:

Команда без -s при двух устройствах: в скриптах либо -s везде, либо ANDROID_SERIAL. Вечное зависание wait-for-device: устройство unauthorized или offline, ставьте timeout и при срабатывании печатайте adb devices в лог. Битый парсинг: забытый tr -d '\r'. Установка сразу после ребута: ждите sys.boot_completed, а не только wait-for-device. И не вписывайте adb kill-server в скрипты: на CI-агенте, где параллельно крутятся несколько джобов, один скрипт убьёт сервер под другим, а все они делят порт 5037 (подробнее в главе 29).

В следующей главе решим те же задачи из Python и Kotlin, где парсить текст почти не придётся, а в главе 24 соберём из этих кирпичей полноценную тестовую автоматизацию.
👍3 ❤️3 🔥3 😄 🤔2
✔ Лучший ответ сформирован автоматически — haskell_ninja
android_roman писал(а):Главная ловушка: wait-for-device отпускает сразу, как только adbd поднялся а как дождаться не только загрузки, но и разблокировки экрана? после ребута первый тест у меня стабильно падает, потому что висит локскрин. пока костыль: после boot_completed шлю input keyevent 82 и молюсь. есть способ приличнее, через dumpsys window проверять что ли?
Перейти к ответу →
Аватара пользователя
haskell_ninja
Сообщения: 1
Зарегистрирован: 24 май 2026, 14:52

Re: Написание скриптов на Bash/CMD/PowerShell

Сообщение haskell_ninja »

✔ Лучший ответ — сформирован автоматически
android_roman писал(а):Главная ловушка: wait-for-device отпускает сразу, как только adbd поднялся
а как дождаться не только загрузки, но и разблокировки экрана? после ребута первый тест у меня стабильно падает, потому что висит локскрин. пока костыль: после boot_completed шлю input keyevent 82 и молюсь. есть способ приличнее, через dumpsys window проверять что ли?
👍1 ❤️1 🔥 😄 🤔
Аватара пользователя
elixirpilot
Сообщения: 1
Зарегистрирован: 13 май 2026, 09:43

Re: Написание скриптов на Bash/CMD/PowerShell

Сообщение elixirpilot »

для виндовых страдальцев: если adb лежит в Program Files, то for /f с одинарными кавычками ломается на пробеле в пути. лечится usebackq: for /f "usebackq skip=1 tokens=1,2" %%a in (`"C:\Program Files\platform-tools\adb.exe" devices`) do ... я на это в свое время полдня убил, проще конечно просто кинуть platform-tools в путь без пробелов
👍 ❤️1 🔥 😄 🤔
Аватара пользователя
juanjohn
Сообщения: 1
Зарегистрирован: 14 май 2026, 23:37

Re: Написание скриптов на Bash/CMD/PowerShell

Сообщение juanjohn »

дополню по pm clear: если пакет числится активным админом устройства, команда вернет Failed и хоть тресни. сначала adb shell dpm remove-active-admin com.example.app/.AdminReceiver, потом уже clear. наступил на это с корпоративным MDM на самсунгах, в логе скрипта было просто Failed без объяснений
👍 ❤️ 🔥 😄 🤔
Аватара пользователя
esp3295
Сообщения: 2
Зарегистрирован: 05 июн 2026, 05:04

Re: Написание скриптов на Bash/CMD/PowerShell

Сообщение esp3295 »

а цикл по устройствам можно параллелить? у нас стенд на 12 телефонов, батч-установка по очереди занимает вечность. думаю прокинуть серийники в xargs -P 4, но не подерутся ли между собой adb-клиенты за один сервер?
👍 ❤️1 🔥2 😄 🤔1
Ответить
← Предыдущая глава
Взаимодействие с эмуляторами
Следующая глава →
ADB в языках программирования

Все главы курса «ADB: Android Debug Bridge»

Поделиться темой: ✈ Telegram VK

Вернуться в «ADB: Android Debug Bridge»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость