LINUX.ORG.RU

Что должна уметь делать консольная утилита?

 ,


0

1

Чтобы быть удобной пользователю?

  1. во-первых, она может разбирать командную строку и получать из неё имена входных и выходных файлов, а если их там нет, то использовать stdin и stdout.

  2. во-вторых, она должна иметь ключ --help или как-то так (кстати, в каких гайдлайнах это написано?)

  3. в-третьих, она может писать в stderr цветным (красным) текстом, отключать вывод цвета при перенаправлении в пайп, и иметь ключ командной строки для того, чтобы цвет не отключать.

  4. возвращать разные коды возвратов при ошибках (есть ли какие-то стандарты? кроме «меньше нуля»)

  5. утилита может уметь разбирать из командной строки команды с параметрами (по аналогии с git)

  6. понимать локаль и выводить сообщения на языке пользователя.

  7. уметь выводить свою версию (для того, чтобы это указывать в багрепотртах)

что ещё?

★★★

Последнее исправление: Shushundr (всего исправлений: 5)

Что должна уметь делать консольная утилита?

  1. Делать что-то нужное.

  2. С возможностью использования в скриптах.

Всё. Всё остальное это extra, на самом деле.

должна иметь ключ –help или как-то так

--help и -h;

она может писать в stderr цветным (красным) текстом, отключать вывод цвета при перенаправлении в пайп, и иметь ключ командной строки для того, чтобы цвет не отключать.

Не обязательно (хотя желательно). Должна возвращать ноль при успехе и не ноль при поражении - вот это важнее.

Bfgeshka ★★★★★
()

В целом правильно. Только пункт 5 опционален и чаще не нужен, чем нужен.

Добавить можно, что версию желательно выводить по --version, а помощь также хорошо бы -h (если эта буква не требуется для human-readable — актуально для утилит, выводящих размеры файлов и подобного в байтах по умолчанию). Также нужнен ключ -q и --quiet для «тихого» режима, то есть без вывода чего-либо кроме ошибок (если, конечно, смысл утилиты не чисто в отображении инфы, по типу ls — там не нужно).

Ну и стоит добавить (это подразумевается, но вроде мы тут банальные вещи пишем, так что), что ошибки и debug всякий отправлять надо в stderr, а собственно вывод команды (если он есть) — в stdout. Не наоборот и не всё в кучу. Зависит от утилиты, конечно, где-то может быть и что-то одно (например если она кроме ошибок ничего писать не умеет).

Если программа умеет принимать много аргументов - имён файлов, и она делает с каждым из этих файлов одно и то же, то ещё стоит добавить --recursive и -r или -R, позволяющий дать ей вместо кучи имён файлов имя директории, в которой, а также во всех её поддиректориях все файлы будут обработаны точно так же.

CrX ★★★★★
()
Последнее исправление: CrX (всего исправлений: 1)
Ответ на: комментарий от CrX

пункт 5 опционален и чаще не нужен, чем нужен

Какие-то параметры всё равно должны как-то уметь передаваться. Например, если утилита делает одно и то же с файлами, то возможно, что есть нюансы. Или надо DEFINE-ы внутрь засунуть. Или ещё что-нибудь.

Например те же даты могут быть в файлах не в такой локали, как установленная для программы. Или кодировке, или формат даты не ISO.

Shushundr ★★★
() автор топика
Последнее исправление: Shushundr (всего исправлений: 1)
Ответ на: комментарий от Shushundr

Какие-то параметры всё равно должны как-то уметь передаваться. Например, если утилита делает одно и то же с файлами, то возможно, что есть нюансы.

Да, параметры. Опциональные ключи, через --. Не команды, как в git. Именно команды (под-команды) как в git мало где нужны. Нужны они очень большим утилитам, умеющих делать много разных действий, а не просто с нюансами. Одно дело скачать, другое удалить, третье запушить на сервер — это совсем разные действия, и вот для них нужны под-команды (у каждой могут быть или не быть свои параметры). Для большинства же утилит, которые делают что-то одно, но с разнообразными нюансами, нужны только собственные параметры, а под-команды не нужны.

Кстати, если утилита делает так много всего, часто (хоть и не всегда) это повод подумать, а точно ли оно должно быть всё одним комбаином, а не несколькими разными утилитами. В git это оправдано, но это скорее особый случай.

CrX ★★★★★
()
Последнее исправление: CrX (всего исправлений: 2)
  1. 0 в случае успеха. 1 в случае ошибки и 2 в случае запуска с неверными аргументами.
u5er ★★
()
Ответ на: комментарий от u5er

Это что-то странное. 22 же с неверными аргументами. А ошибок много разных может быть.

Вот идея получше для более-менее стандартных кодов ошибок:

cat /usr/include/asm-generic/errno-base.h

/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
#ifndef _ASM_GENERIC_ERRNO_BASE_H
#define _ASM_GENERIC_ERRNO_BASE_H

#define    EPERM         1    /* Operation not permitted */
#define    ENOENT        2    /* No such file or directory */
#define    ESRCH         3    /* No such process */
#define    EINTR         4    /* Interrupted system call */
#define    EIO           5    /* I/O error */
#define    ENXIO         6    /* No such device or address */
#define    E2BIG         7    /* Argument list too long */
#define    ENOEXEC       8    /* Exec format error */
#define    EBADF         9    /* Bad file number */
#define    ECHILD       10    /* No child processes */
#define    EAGAIN       11    /* Try again */
#define    ENOMEM       12    /* Out of memory */
#define    EACCES       13    /* Permission denied */
#define    EFAULT       14    /* Bad address */
#define    ENOTBLK      15    /* Block device required */
#define    EBUSY        16    /* Device or resource busy */
#define    EEXIST       17    /* File exists */
#define    EXDEV        18    /* Cross-device link */
#define    ENODEV       19    /* No such device */
#define    ENOTDIR      20    /* Not a directory */
#define    EISDIR       21    /* Is a directory */
#define    EINVAL       22    /* Invalid argument */
#define    ENFILE       23    /* File table overflow */
#define    EMFILE       24    /* Too many open files */
#define    ENOTTY       25    /* Not a typewriter */
#define    ETXTBSY      26    /* Text file busy */
#define    EFBIG        27    /* File too large */
#define    ENOSPC       28    /* No space left on device */
#define    ESPIPE       29    /* Illegal seek */
#define    EROFS        30    /* Read-only file system */
#define    EMLINK       31    /* Too many links */
#define    EPIPE        32    /* Broken pipe */
#define    EDOM         33    /* Math argument out of domain of func */
#define    ERANGE       34    /* Math result not representable */

#endif
CrX ★★★★★
()
Последнее исправление: CrX (всего исправлений: 1)
Ответ на: комментарий от CrX

Не, речь идёт не про errno, а про код, который возвращает утилита. errno - это относится к библиотечным функциям.

u5er ★★
()
Ответ на: комментарий от u5er

А, вон оно чо.

Но всё же ошибок-то тоже бывает много разных, не на все же 1 возвращать. По крайней мере стандартные утилиты разное возвращают. Правда никакой закономерности я в этом не нашёл, равно как и хоть какого-то стандарта.

Или правда нынче на всё 1, без какой-либо дифференциации?

CrX ★★★★★
()
Последнее исправление: CrX (всего исправлений: 1)
Ответ на: комментарий от CrX

хоть какого-то стандарта.

Стандарт тут только один: нулевое значение - успех, не нулевое - ошибка. Конкретные коды можно в документации расписать.

u5er ★★
()
Ответ на: комментарий от u5er

Никто не мешает тебе возвращать код errno в случае если программа далее не может работать по одной из причин в ряде кодов errno. Сначала коды errno затем свои специфические. Но тут опять же всё исходя из ситуации, если в программе просто нет ничего или очень мало что описано в errno то проще как у тебя выше. Можно и так и сяк .

LINUX-ORG-RU ★★★★★
()
Последнее исправление: LINUX-ORG-RU (всего исправлений: 1)
Ответ на: комментарий от u5er

Стандарт тут только один: нулевое значение - успех, не нулевое - ошибка.

Не, ну это понятно. Странно, что никто не сделал хоть какого-то стандарта для всех утилит. Ну типа например если ошибка работы с файлами, один код, аргументы неправильные — другой, просто возврат false — единичка, и т.д. Стандартных ошибок не так уж много. Вообще я бы errno возвращал. Ну а почему бы нет? А после 34 уже свои утилитоспецифичные, расписанные в документации. Хоть какая-то стандартизация.

CrX ★★★★★
()
Ответ на: комментарий от LINUX-ORG-RU

Это так, но на деле это особого смысла не имеет. Основная информация об ошибке идёт текстом в stdout и stderr. Для скриптов-обёрток достаточно писать логику так, что 0 - успех, всё остальное - ошибка. По крайней мере, на практике я другого не встречал нигде.

u5er ★★
()
Ответ на: комментарий от u5er

Для скриптов-обёрток достаточно писать логику так, что 0 - успех, всё остальное - ошибка. По крайней мере, на практике я другого не встречал нигде.

Я в скриптах делал для grep различие между 0, 1 и остальным. Он выдаёт 1 в случае, если успешно поискал, но нричего не нашёл (нет нужного фрагмента в файле), а 2 (или может и выше тоже может) в случае именно ошибки (например permission denied или файла нет такого). Иногда это важно — знать именно ничего не найдено, или именно ошибка.

CrX ★★★★★
()
Последнее исправление: CrX (всего исправлений: 1)

в-третьих, она может писать в stderr цветным (красным) текстом, отключать вывод цвета при перенаправлении в пайп, и иметь ключ командной строки для того, чтобы цвет не отключать.

Много ты видел команд, которые красным пишут? Кроме тех, что написаны на Расте или Го я не припомню.

Забыл еще важный пункт — поставляться с man-страницей.

urxvt ★★★★★
()
Ответ на: комментарий от u5er

Так то да, я сам так делаю, просто по завершению нормальному 0, а с каждым разным выходом по нештатной ситуации или/и ситуации когда желаемое не до достигнуто просто разный код N+1.

Но некоторые утилиты (не помню какие) например на --help или --version возвращают 1 :D и тут беда на самом деле, ну то есть часто некоторые в случае падения пишут просто что-то вроде exit(1) и всё, а я например хочу дёрнуть appname --version дабы узнать версию, если я дёргаю утилиту из своего говнокода и вот сиди думай, оно там выдало версию и упало и дальше работать нельзя, или нет и вот такие вот мелочи, но они раздражают, да можно сказать не дёргай утилиты, дёргай библиотеки, но домашние скрипты и всё такое же.

Хотя у меня у самого культуры если так можно сказать на счёт этого не особо нет, 0 норм, 1 что-то сломалось и всё. А надо бы везде прописывать типа

enum
{
  ERR1,
  ERR2,
  ERR3,
  ...
};

const char errors_strid[] = 
{
   [ERR1] = "ERR1 description",
   [ERR2] = "ERR1 description",
   [ERR3] = "ERR1 description"
   ....
};

И засовывать это в --help и генерировать из этого документацию, дабы было всё просто однозначно, пофигу как, просто однозначно. Мол если вот это значит вот это. Тут ещё часто важны константные и не меняющиеся сообщения текстовые ибо люди часто забивают болт на коды возврата и грепают выхлоп ошибки на предмет «волшебного слова» и по хорошему надо ещё по мимо распределения уникальных кодов ошибки ещё и распределять уникальные слова по сообщениям к этим кодам ошибок на stderr.

Ну и как всегда, как делать знаю, но сам так не делаю, атата по жопе мне.

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от urxvt

Да уже много, все помешались на цвете. Иначе бы сайт https://no-color.org/ не существовал.

Вобще с этим цветным выводом какой-то бардак, у одних он включен, у других выключен по умолчанию. Одни понимают NO_COLOR, другие файл в каталоге /etc/terminal-colors.d/, а всякие поделки Васи Пупкина всё время хреначат в цвете, даже если в pipe...

mky ★★★★★
()

ИМХО, конечно, но хорошая программа должна поддерживать –verbose, а лучше если несколько уровней оного.

FishHook
()

И да, если программа имеет сайд-эффекты, например, удаляет что-то с диска, то я был бы благодарен автору за ‘–dry-run’

FishHook
()

ИМХО, необходимость писать краным нужна, только если утилита ещё что-то пишет, не красным. Некоторые утилиты фактически пишут на экран только ошибки. Зачем там ещё цвет добавлять?

По кодам возврата стандарта нет. Вот pppd определил кучу кодов возврата, чтобы его было удобнее вызывать из скриптов. А с большинством консольных утилит не заморачиваются, 0 или 1.

mky ★★★★★
()
Ответ на: комментарий от Shushundr

info гнутое расширение, man стандарт.
Но если на Расте собираешься писать то да, делай без мана и с радужным выводом. Там это тоже стандарт своего рода.

urxvt ★★★★★
()
Ответ на: комментарий от FishHook

Вот, да, супер.

Я забыл (не вспомнил, не знал), что консольная утилита должна ещё обрабатывать сигналы.

Консольная утилита должна корректно обрабатывать сигналы Linux, такие как SIGINT (Ctrl+C), SIGQUIT (Ctrl+) и другие. Обработка сигналов может включать в себя временное прерывание работы утилиты, завершение процесса или выполнение определённых действий перед завершением. Утилита может обрабатывать сигналы с помощью команды trap, которая позволяет задать код обработки сигнала и список сигналов, разделённых пробелами.

Утилита dd обрабатывает сигналы SIGINT и SIGQUIT.

#include <signal.h>

void sigint_handler(int sig) {
    // корректно завершить работу и сохранить данные
    exit(0);
}

int main() {
    signal(SIGINT, sigint_handler);
    // ...
}
Shushundr ★★★
() автор топика
Последнее исправление: Shushundr (всего исправлений: 2)
Ответ на: комментарий от Bfgeshka

Всё.

А вот ни разу не всё. Ключ -h/–help строго обязателен, иначе разбирательство с тем что вообще происходит можно исключительно по коду этой утилиты. Был такой увлекательный (нет) опыт, дюжина утилит с неизвестными обязательными ключами, которые не умели вообще говорить о своём состоянии, т.е. молча падали если где-то ошибка. Автор сего поделия был недоступен.

einhander ★★★★★
()
Ответ на: комментарий от Bfgeshka

У утилиты есть хоть один юзер - автор. Я сомневаюсь, что у ТС идеальная память. А напомнить самому себе, чего наворотил и как этим пользоваться всё же стоит.

einhander ★★★★★
()
Ответ на: комментарий от einhander

Ну, если серьёзно говорить, то я согласен, и в свои я тоже всегда пишу помощь. Потому и указал флаги в своём первом посте.

Bfgeshka ★★★★★
()

Для протопирования, да и вообще написания простых вещей лучше всего подходит Python:

#!/usr/bin/env python
import argparse
import sys
import functools

__version__ = '0.1.0'

print_err = functools.partial(print, file=sys.stderr)



def main(argv: list[str] = None) -> None | int:
    parser = argparse.ArgumentParser(description="Описание")
    parser.add_argument(
        "-i",
        "--input",
        type=argparse.FileType(),
        help="файл с входными данными",
        default="-",
    )
    parser.add_argument(
        "-o",
        "--output",
        type=argparse.FileType("w"),
        help="файл для сохранения результатов",
        default="-",
    )
    parser.add_argument(
        "-V",
        "--version",
        action="version",
        version=f"%(prog)s {__version__}",
    )
    args = parser.parse_args(argv)
    # делаешь что-то
    args.input
    args.output

    if something_wrong:
        print_err("Это сообщение будет выведено в stderr")
        # код ошибки 2
        return 2

    # если функция ничего не возвращает явно, то она вернет None
    # который равен коду 0


if __name__ == "__main__":
    sys.exit(main())

Подкоманды так легко добавляются

rtxtxtrx ★★
()

Иметь ключ на работу в скриптовом режиме без интерактива, обычно это -b, и ключ отформатированный для машины вывод.

Vsevolod-linuxoid ★★★★★
()
Ответ на: комментарий от Vsevolod-linuxoid

и ключ отформатированный для машины вывод

И на каком языке этот «независимый от людей» вывод должен быть, на русском или на английском ?

Shushundr ★★★
() автор топика
Ответ на: комментарий от token_polyak

✠ ✡ ✢ ✣ ✤ ✥ ✦ ✧ ✩ ✪ ✫ ✬ ✭ ✮ ✯ ✰ ✱ ✲ ✳ ✴ ✵ ✶ ✷ ✸ ✹ ✺ ✻ ✼ ✽ ✾ ✿ ❀ ❁ ❂ ❃ ❄ ❅ ❆ ❇ ❈ ❉ ❊ ❋

Да вы просто дед мороз! Ни одной одинаковой снежинки.

Shushundr ★★★
() автор топика
Ответ на: комментарий от Shushundr

На таком, чтобы его было удобно парсить через bash, cut, grep, sed, awk и прочее.

Никогда скрипты не писал что ли, и не сталкивался с тем, что приходится примерять хитрости для обработки отформатированного под удобный просмотр глазами вывод?

Vsevolod-linuxoid ★★★★★
()
Ответ на: комментарий от Vsevolod-linuxoid

Иметь ключ на работу в скриптовом режиме без интерактива

Это требуется только если интерактивный режим вообще предусмотрен. Далеко не для всех утилит он нужен. Даже, наверное, для большинства не нужен.

CrX ★★★★★
()

Выводить цвета совершенно не обязательно.

А главный пункт: удобно для пользователя решать поставленную задачу. Всем остальным можно пренебречь. Ну или всем кроме справки. Если прям удобно не получается - можно чтобы просто решала задачу - в конце конццов консоль это про автоматизацию, простоту (в т.ч. разработки) и экономию ресурсов.

kirill_rrr ★★★★★
()
Ответ на: комментарий от einhander

Очень стоит. Особенно когда автор - ты и за 5 лет накопил с десяток версий, раскиданных по разным компам в произвольном порядке. У меня со скриптами так.

kirill_rrr ★★★★★
()
Последнее исправление: kirill_rrr (всего исправлений: 1)
Ответ на: комментарий от CrX

Если программа умеет принимать много аргументов - имён файлов, и она делает с каждым из этих файлов одно и то же, то ещё стоит добавить –recursive и -r или -R, позволяющий дать ей вместо кучи имён файлов имя директории, в которой, а также во всех её поддиректориях все файлы будут обработаны точно так же.

Согласен, но, вообще-то, это противоречит идеологии KISS

tiinn ★★★★★
()
Ответ на: комментарий от tiinn

KISS — это очень хорошо, но не до фанатизма. Во всём мера должна быть, даже в следовании той или иной идеологии. Крайний радикализм ни к чему хорошему обычно не приводит. Ну и вот на мой взгляд, отказ от рекурсивного обхода директорий, если это может быть полезно для типичного юзкейса использования утилиты (и соответственно, пускай юзер через find запускает кучу раз) — это уже слегка за гранью здравого следования KISS. Слегка, но всё же за гранью.

Ну и вообще надо нюансы учитывать. Если типичный способ использования утилиты — обработка одного файла, но изредка кто-то хочет batch’нуть по всем — не стоит добавлять рекурсивный режим, пусть через find делают. Если же типично, что обрабатывается много файлов, уже стоит подумать об этом ключе. Если же программа делает какую-то тяжёлую операцию, или операцию, связанную с I/O и применяет результат ко многим файлам, рекурсивный режим обязателен. Ну там, например, получил какую-то инфу c удалённого сервера и по результату применяешь ко всем файлам. Через find придётся много раз медленную операцию загрузки с сервера делать, что уже маразм.

CrX ★★★★★
()
Ответ на: комментарий от CrX

И еще -- в контексте «Ша! Дальше ключи не парсим.», если предполается работа со значениями, которые могут начинаться с дефиса.

Radjah ★★★★★
()
Ответ на: комментарий от rtxtxtrx

sys.exit(main())

Дожили, мало того, что без отступов программировать не можем, так ещё и программа не может завершиться без костылей…

Exmor_RS ★★★
()

Уметь ловить и отрабатывать сигналы будучи запущенной в фоне?

Уметь работать демоном?

targitaj ★★★★★
()
Ответ на: комментарий от targitaj

Ну, это не всегда имеет смысл, например какой смысл в htop & уйдёт в фон и ничего не видно, хотя иногда бывает удобно нажать CTRL+Z убирая интерактивную программу с глаз долой, чтобы что-то потыкать ещё, а потом ввести fg вернув назад на своё место, тут конечно не про то, но сигналы ловить да.

Может ещё сюда глянуть

apt source hello

Для гнутого софта можно это вообще брать как шаблон для дальнейшей разработки. (хотя всякого обсуждаемого выше там нету)

LINUX-ORG-RU ★★★★★
()
Последнее исправление: LINUX-ORG-RU (всего исправлений: 2)
Ответ на: комментарий от targitaj

Уметь работать демоном?

С systemd это вроде уже не обязательно. Максимум надо будет переварить, что stdin отвалится. Сервер factorio на этом месте warning рисует, но работать не перестаёт.

Radjah ★★★★★
()
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.