Unary operator expected linux

Частые ошибки программирования на Bash

Качество скриптов, используемых для автоматизации и оптимизации работы системы, является залогом ее стабильности и долголетия, а также сохраняет время и нервы администратора этой системы. Несмотря на кажущуюся примитивность bash как языка программирования, он полон подводных камней и хитрых течений, способных значительно подпортить настроение как разработчику, так и администратору.

Большинство имеющихся руководств посвящено тому, как надо писать. Я же расскажу о том, как писать НЕ надо 🙂

Данный текст является вольным переводом вики-страницы «Bash pitfalls» по состоянию на 13 декабря 2008 года. В силу викиобразности исходника, этот перевод может отличаться от оригинала. Поскольку объем текста слишком велик для публикации целиком, он будет публиковаться частями.

1. for i in `ls *.mp3`

Одна из наиболее часто встречающихся ошибок в bash-скриптах — это циклы типа такого:

Это не сработает, если в названии одного из файлов присутствуют пробелы, т.к. результат подстановки команды ls *.mp3 подвергается разбиению на слова. Предположим, что у нас в текущей директории есть файл 01 — Don’t Eat the Yellow Snow.mp3 . Цикл for пройдётся по каждому слову из названия файла и $i примет значения: «01» , «-» , «Don’t» , «Eat» , «the» , «Yellow» , «Snow.mp3» .

Заключить всю команду в кавычки тоже не получится:

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

На самом деле использование ls совершенно излишне: это внешняя команда, которая просто не нужна в данном случае. Как же тогда правильно? А вот так:

Предоставьте bash’у самому подставлять имена файлов. Такая подстановка не будет приводить к разделению строки на слова. Каждое имя файла, удовлетворяющее шаблону *.mp3 , будет рассматриваться как одно слово, и цикл пройдёт по каждому имени файла по одному разу.

Дополнительные сведения можно найти в п. 20 Bash FAQ.

Внимательный читатель должен был заметить кавычки во второй строке вышеприведённого примера. Это плавно подводит нас к подвоху №2.

2. cp $file $target

Что не так в этой команде? Вроде бы ничего особенного, если вы абсолютно точно знаете, что переменные $file и $target не содержат пробелов или подстановочных символов.

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

Без двойных кавычек скрипт выполнит команду cp 01 — Don’t Eat the Yellow Snow.mp3 /mnt/usb , и вы получите массу ошибок типа cp: cannot stat `01′: No such file or directory . Если в значениях переменных $file или $target содержатся символы *, ?, [..] или (..), используемые в шаблонах подстановки имен файлов («wildmats»), то в случае существования файлов, удовлетворяющих шаблону, значения переменных будут преобразованы в имена этих файлов. Двойные кавычки решают эту проблему, если только «$file» не начинается с дефиса — , в этом случае cp думает, что вы пытаетесь указать ему еще одну опцию командной строки.

Один из способов обхода — вставить двойной дефис ( — ) между командой cp и её аргументами. Двойной дефис сообщит cp , что нужно прекратить поиск опций:

Однако вам может попасться одна из систем, в которых такой трюк не работает. Или же команда, которую вы пытаетесь выполнить, не поддерживает опцию — . В таком случае читайте дальше.

Ещё один способ — убедиться, что названия файлов всегда начинаются с имени каталога (включая ./ для текущего). Например:

Даже если у нас есть файл, название которого начинается с «-«, механизм подстановки шаблонов гарантирует, что переменная будет содержать нечто вроде ./-foo.mp3 , что абсолютно безопасно для использования вместе с cp .

3. [ $foo = «bar» ]

В этом примере кавычки расставлены неправильно: в bash нет необходимости заключать строковой литерал в кавычки; но вам обязательно следует закавычить переменную, если вы не уверены, что она не содержит пробелов или знаков подстановки (wildcards).

Этот код ошибочен по двум причинам:

1. Если переменная, используемая в условии [ , не существует или пуста, строка

будет воспринята как

что вызовет ошибку «unary operator expected». (Оператор «=» бинарный, а не унарный, поэтому команда [ будет в шоке от такого синтаксиса)
2. Если переменная содержит пробел внутри себя, она будет разбита на разные слова перед тем, как будет обработана командой [ :

Читайте также:  Linux expand root partition

Даже если лично вам кажется, что это нормально, такой синтаксис является ошибочным.

Правильно будет так:

Но этот вариант не будет работать, если $foo начинается с — .

В bash для решения этой проблемы может быть использовано ключевое слово [[ , которое включает в себя и значительно расширяет старую команду test (также известную как [ )

Внутри [[ и ]] уже не нужно брать в кавычки названия переменных, поскольку переменные больше не разбиваются на слова и даже пустые переменные обрабатываются корректно. С другой стороны, даже если лишний раз взять их в кавычки, это ничему не повредит.

Возможно, вы видели код типа такого:

Хак x»$foo» требуется в коде, который должен работать в шеллах, не поддерживающих [[ , потому что если $foo начинается с — , команда [ будет дезориентирована.

Если одна из частей выражения — константа, можно сделать так:

Команду [ не волнует, что выражение справа от знака «=» начинается с — . Она просто использует это выражение, как строку. Только левая часть требует такого пристального внимания.

4. cd `dirname «$f»`

Пока что мы в основном говорим об одном и том же. Точно так же, как и с раскрытием значений переменных, результат подстановки команды подвергается разбиению на слова и раскрытию имен файлов (pathname expansion). Поэтому мы должны заключить команду в кавычки:

Что здесь не совсем очевидно, это последовательность кавычек. Программист на C мог бы предположить, что сгруппированы первая и вторая кавычки, а также третья и четвёртая. Однако в данном случае это не так. Bash рассматривает двойные кавычки внутри команды как первую пару, и наружные кавычки — как вторую.

Другими словами, парсер рассматривает обратные кавычки ( ` ) как уровень вложенности, и кавычки внутри него отделены от внешних.

Такого же эффекта можно достичь, используя более предпочтительный синтаксис $() :

Кавычки внутри $() сгруппированы.

5. [ «$foo» = bar && «$bar» = foo ]

Нельзя использовать && внутри «старой» команды test или её эквивалента [ . Парсер bash’а видит && вне скобок и разбивает вашу команду на две, перед и после && . Лучше используйте один из вариантов:

Обратите внимание, что мы поменяли местами константу и переменную внутри [ — по причинам, рассмотренным в предыдущем пункте.

То же самое относится и к || . Используйте [[ , или -o , или две команды [ .

6. [[ $foo > 7 ]]

Если оператор > используется внутри [[ ]] , он рассматривается как оператор сравнения строк, а не чисел. В некоторых случаях это может сработать, а может и не сработать (и это произойдёт как раз тогда, когда вы меньше всего будете этого ожидать). Если > находится внутри [ ] , всё ещё хуже: в данном случае это перенаправление вывода из файлового дескриптора с указанным номером. В текущем каталоге появится пустой файл с названием 7 , и команда test завершится с успехом, если только переменная $foo не пуста.

Поэтому операторы > и [ .. ] или [[ .. ]] использовать нельзя.

Если вы хотите сравнить два числа, используйте (( )) :

Если вы пишете для Bourne Shell (sh), а не для bash, правильным способом является такой:

Обратите внимание, что команда test . -gt . выдаст ошибку, если хотя бы один из её аргументов — не целое число. Поэтому уже не имеет значения, правильно ли расставлены кавычки: если переменная пустая, или содержит пробелы, или ее значение не является целым числом — в любом случае возникнет ошибка. Просто тщательно проверяйте значение переменной перед тем, как использовать её в команде test .

Двойные квадратные скобки также поддерживают такой синтаксис:

7. count=0; grep foo bar | while read line; do ((count++)); done; echo «number of lines: $count»

На первый взгляд этот код выглядит нормально. Но на деле переменная $count останется неизменной после выхода из цикла, к большому удивлению bash-разработчика. Почему так происходит?

Каждая команда в конвейере выполняется в отдельной подоболочке (subshell), и изменения в переменной внутри подоболочки не влияют на значение этой переменной в родительском экземпляре оболочки (т.е. в скрипте, который вызвал этот код).

В данном случае цикл for является частью конвейера и выполняется в отдельной подоболочке со своей копией переменной $count , инизиализированной значением переменной $count из родительской оболочки: «0». Когда цикл заканчивается, использованная в цикле копия $count отбрасывается и команда echo показывает неизменённое начальное значение $count («0»).

Обойти это можно несколькими способами.

Можно выполнить цикл в своей подоболочке (слегка кривовато, но так проще и понятней и работает в sh):

Чтобы полностью избежать создания подоболочки, используйте перенаправление (в Bourne shell (sh) для перенаправления также создаётся subshell, поэтому будьте внимательны, такой трюк сработает только в bash):

Предыдущий способ работает только для файлов, но что делать, если нужно построчно обработать вывод команды? Используйте подстановку процессов:

Ещё пара интересных способов разрешения проблемы с субшеллами обсуждается в Bash FAQ #24.

8. if [grep foo myfile]

Многих смущает практика ставить квадратные скобки после if и у новичков часто создаётся ложное впечатление, что [ является частью условного синтаксиса, так же, как скобки в условных конструкциях языка C.

Читайте также:  Что такое цифровое разрешение windows 10

Однако такое мнение — ошибка! Открывающая квадратная скобка ( [ ) — это не часть синтаксиса, а команда, являющаяся эквивалентом команды test , лишь за тем исключением, что последним аргументом этой команды должна быть закрывающая скобка ] .

Как видите, в синтаксисе if нет никаких [ или [[ !

Ещё раз, [ — это команда, которая принимает аргументы и выдаёт код возврата; как и все нормальные команды, она может выводить сообщения об ошибках, но, как правило, ничего не выдаёт в STDOUT.

if выполняет первый набор команд, и в зависимости от кода возврата последней команды из этого набора определяет, будет ли выполнен блок команд из секции «then» или же выполнение скрипта продолжится дальше.

Если вам необходимо принять решение в зависимости от вывода команды grep , вам не нужно заключать её в круглые, квадратные или фигурные скобки, обратные кавычки или любой другой синтаксический элемент. Просто напишите grep как команду после if :

Обратите внимание, что мы отбрасываем стандартный вывод grep : нам не нужен результат поиска, мы просто хотим знать, присутствует ли строка в файле. Если grep находит строку, он возвращает 0, и условие выполняется; в противном случае (строка в файле отсутствует) grep возвращает значение, отличное от 0. В GNU grep перенаправление >/dev/null можно заменить опцией -q , которая говорит grep ‘у, что ничего выводить не нужно.

9. if [bar=»$foo»]

Как было объяснено в предыдущем параграфе, [ — это команда. Как и в случае любой другой команды, bash предполагает, что после команды следует пробел, затем первый аргумент, затем снова пробел, и т.д. Поэтому нельзя писать всё подряд без пробелов! Правильно вот так:

bar , = , «$foo» (после подстановки, но без разделения на слова) и ] являются аргументами команды [ , поэтому между каждой парой аргументов обязательно должен присутствовать пробел, чтобы шелл мог определить, где какой аргумент начинается и заканчивается.

10. if [ [ a = b ] && [ c = d ] ]

Снова та же ошибка. [ — команда, а не синтаксический элемент между if и условием, и тем более не средство группировки. Вы не можете взять синтаксис C и переделать его в синтаксис bash простой заменой круглых скобок на квадратные.

Если вы хотите реализовать сложное условие, вот правильный способ:

Заметьте, что здесь у нас две команды после if, объединённые оператором &&. Этот код эквивалентент такой команде:

Если первая команда test возвращает значение false (любое ненулевое число), тело условия пропускается. Если она возвращает true , выполняется второе условие; если и оно возвращает true , то выполняется тело условия.

Продолжение следует.

Первая публикация этого перевода происходила на страницах моего блога.

Источник

Bash Unary Operator Expected: What Does It Mean?

The first time I saw the error “Bash Unary Operator Expected” in the Linux terminal I thought:”And now? What do I do?” 😀

We will understand this error together and we will find a solution for it.

What is the Bash Unary Operator Expected error?

It’s an error that occurs when Bash identifies a line in your script that contains a binary operator that is not applied to two arguments. When this happens Bash assumes that you want to use a unary operator instead and raises the Unary Operator Expected error.

It might not be 100% clear if it’s the first time you hear about unary and binary operators.

That’s ok we will look at this together and by the end of this article you will know how to fix this error in your scripts.

Let’s figure it out!

What Causes The Bash Unary Operator Expected Error

The best way to understand an error is to write code where the error occurs and then go through any changes required to fix the error.

So, let’s follow this principle!

I will start with a simple Bash script called unary.sh:

When I run the script I get the following output:

Now, let’s update line 3 of our script from:

After this change the variable DAY is empty.

Let’s run the script again…

This time we see the error “unary operator expected”. Why?

Sometimes Bash errors don’t make much sense, or at least it looks like they don’t.

Why Bash is expecting a unary operator? And what is a unary operator?

Bash Unary and Binary Operators

What are unary and binary operators?

Bash operators are used in expressions that verify conditions as part of the logic of a script. Unary operators apply to one argument and are often used to verify the status of a file (e.g. does a specific file exist?). Binary operators apply to two arguments and are used, for example, as part of string or arithmetic comparisons (e.g. is a bigger than b?).

Now the error we are seeing makes a bit more sense.

At line 5 of our script Bash is expecting a unary operator but the one we are using ( == ) is a binary operator.

Читайте также:  Системное администрирование линукс с нуля

It still doesn’t make complete sense but we are getting there…

Here is what happens when Bash reaches line 5 of the script:

It replaces $DAY with its value, that in the first example is “monday”, so the line becomes:

This expression is true and the echo command inside the if statement gets executed.

The operator == is a binary operator because it compares two values (arguments), the one on the left side of the operator with the one on the right side.

But, what happens when the value of the variable DAY is empty?

Line 5 of our script becomes:

As you can see the left side of the comparison expression disappears and that’s why Bash prints the error “unary operator expected”.

Because the expression doesn’t contain two values anymore, but only one, “monday”.

How to Fix the Bash Unary Operator Expected Error

Now that we have a better understanding of the error, we also want to understand how to fix it.

How can we make sure our original expressions keeps both arguments even when the variable DAY is empty?

The solution is surrounding the variable $DAY in the comparison expression with double quotes.

The script becomes:

Notice how $DAY has become “$DAY”…

This time, when the variable DAY is empty and Bash reaches line 5 of the code, the if statement becomes:

In this way the comparison expression is still valid. In this case the value of the expression is false and the script doesn’t print any output (and doesn’t error in the way we have seen before):

Does it make sense?

Bash Unary Operator Expected with While Loop

Let’s look at another script in which the same error occurs.

This time we will use a while loop in our script…

I have created a simple script that uses a while loop to print the value of the variable INDEX and increase its value until it reaches 5.

When I run the script the error shows up again:

One way to troubleshoot errors in a Bash script is by running the script using the following command:

Can you see how the value of the INDEX variable inside the test expression of the while loop increases at every iteration?

The script stops when INDEX reaches the value 5 because at that point the test expression of the while loop becomes false.

Unary Operator Expected when Checking For Empty String

One way to check if a string is empty in Bash is using the following if statement:

For the reasons explained in the previous sections the if statement will cause the “unary operator expected” error considering that the variable MYSTRING is empty.

Does it make sense?

As we have learned, one workaround would be surrounding $MYSTRING in the if statement with double quotes:

The script works well this time:

To achieve the same purpose we can replace the binary operator == with the unary operator -z, that is used to verify if a string is empty.

If we use the -z test operator, the script becomes cleaner:

You can run it and make sure you receive the output “The string MYSTRING is empty”.

Test Expression with Double Square Brackets

Another way to solve the error we are learning about in this article, is by using test expressions with double square brackets ( [[ … ]] ) instead of single square brackets ( [ … ] ).

Let’s see how it works for the script that wasn’t working before (I have replaced [] with [[ ]]):

The script works fine:

We can see why using the bash -x command:

As you can see, this time even if the variable MYSTRING is empty the comparison in the if statement contains empty strings for both arguments:

And for this reason we don’t see the unary operator expected error.

In a future article I will explain the difference between single and double brackets in Bash with some examples.

Conclusion

Quite an interesting error this one!

Now you know what to do if you see the “unary operator expected” error and you also know which parts of your scripts are likely to cause it.

In summary, the first place to look for is a Bash binary operator that compares the value of a variable with either a string or a number.

Once you find it have a look at what could make that variable empty and also if you are surrounding that variable with double quotes.

And you? Where are you seeing this error in your scripts?

Let me know in the comments below 🙂

Related FREE Course: Decipher Bash Scripting

Источник

Оцените статью