Аргументы командной строки в Go
Большинство программ учитывают в своей работе некоторые входные данные для генерации некоторых выходных данных. Но как получить входные данные для работы программы? Некоторые программы генерируют собственные данные, но чаще всего ввод поступает из внешнего источника: файла, подключения к сети, вывода из другой программы, ввода пользователя с помощью клавиатуры, из аргументов командной строки, итд, итп.
Пакет os
предоставляет функции и различные значения
для работы с операционной системой платформо-независимым образом.
Аргументы командной строки в программе на Golang доступны в виде переменной с именем Args
,
которая является частью пакета os
поэтому, ее имя в любом месте за пределами родного пакета выглядит как os.Args
.
Переменная os.Args
представляет собой срез (slice) строк.
Срезы являются фундаментальным понятием в Go, и вскоре мы поговорим о них более подробно.
Пока же думайте о срезе как о некоей последовательности s
элементов массива с динамическим размером,
в которой к отдельным элементам можно обратиться как к s[i]
,
а к непрерывной подпоследовательности — как к s[m:n]
.
Количество этих элементов определяется как len(s)
.
Индексация в Go использует полуоткрытые интервалы, которые включают первый индекс, но исключают последний, потому что это упрощает логику.
Например, срез s[m: п], где 0 < m < n < len(s), содержит n-m элементов.
Первый элемент os.Args, os.Args[0]
, представляет собой имя самой команды;
остальные элементы представляют собой аргументы, которые были переданы программе, когда началось ее выполнение.
Выражение вида s[m:n]
дает срез, который указывает
на элементы от m
до n-1
- то есть не включая n
.
Если опущено значение m
или n
, используются значения по умолчанию — 0 или len(s) соответственно,
так что мы можем сократить запись нужного нам среза до os.Args [1:]
что будет означать "со второго по списку значения и до конца".
Давайте реализуем команду Unix echo, которая выведет нам в одну строку аргументы, переданные в командной строке. Она импортирует два пакета, которые указаны в круглых скобках, а не в виде отдельных объявлений импорта. Можно использовать любую запись, но обычно используется список. Порядок импорта значения не имеет; инструмент gofmt отсортирует имена пакетов в алфавитном порядке.
// Выводим аргументы командной строки package main import ( "fmt" "os" ) func main() { var s, sep string for i := 1; i < len(os.Args); i++ { s += sep + os.Args[i] sep = " " } fmt.Println(s) }
По соглашению мы обязаны описывать каждый свой пакет в комментарии
, непосредственно предшествующем его объявлению;
для пакета main
этот комментарий состоит из одного или нескольких полных предложений, которые описывают программу в целом.
Объявление var
объявляет две переменные — s
и sep
типа string
.
Иногда, как часть объявления, переменная может быть тут же инициализирована.
Если переменная не инициализирована явно, она неявно инициализируется нулевым значением соответствующего типа
(которое равно 0 для числовых типов и пустой строке " " для строк).
Таким образом, в нашей программе объявление неявно инициализирует строки s
и sep
.
Для чисел Go предоставляет обычные арифметические и логические операторы.
Однако при применении к строкам оператор +
выполняет конкатенацию их значений,
так что выражение sep + os.Args[i]
представляет собой конкатенацию строк sep
и os.Args[i]
.
Использованная в программе инструкция s += sep + os.Args[i]
представляет собой инструкцию присваивания,
которая выполняет конкатенацию старого значения s
с sep
и os.Args[i]
и присваивает новое значение переменной s
;
она эквивалентна выражению s = s + sep + os.Args[i]
.
Оператор +=
является присваивающим оператором.
Программа echo
могла бы вывести все выходные данные в цикле по одному фрагменту за раз,
но наша версия этого мини приложения вместо этого строит строку, добавляя новый текст в конце каждого фрагмента.
Изначально строка s
пуста, т.е. имеет значение и каждая итерация цикла добавляет к ней текст.
После первой итерации перед очередным фрагментом вставляется пробел, так что после завершения всего цикла между всеми аргументами имеются пробелы.
Этот процесс имеет квадратичное время работы, так что он может оказаться дорогостоящим,
если количество аргументов будет большим, но для echo это маловероятно.
Индексная переменная цикла i объявлена в первой части цикла for.
Символы :=
являются частью краткого объявления переменной,
инструкции, которая объявляет одну или несколько переменных и назначает им соответствующие типы на основе значения инициализатора.
Оператор инкремента i++
добавляет к i единицу.
Эта запись эквивалентна записи i += 1
, которая, в свою очередь, эквивалентна записи i = i + 1
.
Это инструкции, а не выражения, как в большинстве языков семейства С, поэтому j = i++ является некорректной>;
кроме того, эти операторы могут быть только постфиксными.
Цикл for
является единственной инструкцией цикла в языке Go.
Он имеет ряд разновидностей, одна из которых показана здесь:
for инициализация; условие; последействие { // нуль или несколько инструкций }
Вокруг трех компонентов цикла for
скобки не используются.
Фигурные же скобки обязательны, причем открывающая фигурная скобка обязана находиться в той же строке, что и инструкция последействие.
Необязательная инструкция инициализации выполняется до начала работы цикла. Если она имеется в наличии, она обязана быть простой инструкцией, т.е. кратким объявлением переменной, инструкцией инкремента или присваивания, или вызовом функции.
Условие представляет собой логическое выражение, которое вычисляется в начале каждой итерации цикла. Если его вычисление дает результат true, выполняются инструкции тела цикла. Инструкция последействие выполняется после тела цикла, после чего вновь вычисляется условие. Цикл завершается, когда вычисление условия дает значение false.
Любая из перечисленных частей может быть опущена. При отсутствии как инициализации, так и последействия можно опустить точки с запятыми:
// Традиционный цикл "while" for condition { // ... } // Традиционный бесконечный цикл for { // ... }
Еще одна разновидность цикла for выполняет итерации для диапазона значений для типа данных наподобие строки или среза.
for key, value := range os.Args[1:] { s += sep + arg sep = " " }
В этой версии программы для объявления и инициализации s
и sep
используется краткое объявление переменной,
но мы можем объявить эти переменные и отдельно.
Имеются разные способы объявления строковых переменных; приведенные далее объявления эквивалентны:
- s := ""
- var s string
- var s = ""
- var s string = ""
Почему мы должны предпочитать один вид объявления другим? Первая разновидность, краткое объявление переменной, является наиболее компактной, однако может использоваться только внутри функции, но не для переменных уровня пакета. Вторая разновидность основана на инициализации по умолчанию (для строки — значением ""). Третья разновидность используется редко, в основном при объявлении нескольких переменных. Четвертая разновидность содержит явное указание типа переменной, которое является излишним, когда тип совпадает с типом начального значения переменной, но которое является обязательным в других случаях, когда типы переменной и инициализатора разные. На практике обычно следует использовать одну из первых двух разновидностей:
- с явной инициализацией (чтобы указать важность начального значения)
- с неявной инициализацией по умолчанию (чтобы указать, что начальное значение не играет роли)
Как отмечалось выше, при каждой итерации цикла строка s получает новое содержимое.
Оператор +=
создает новую строку путем конкатенации старой строки,
символа пробела и очередного аргумента, а затем присваивает новую строку переменной s
.
Старое содержимое строки s более не используется, поэтому оно будет в надлежащее время обработано сборщиком мусора.
Если объем данных велик, это может быть дорогостоящим решением.
Более простым и более эффективным решением было бы использование функции Join
из пакета strings
:
Println(strings.Join(os.Args[1:], " "))