LINUX.ORG.RU

Баг или фича: expr $456 + $457 = expr 56 + 57

 , ,


0

1

Случайно обнаружил интересную особенность в Bash с переменными. Выглядит это так:

expr $456 + $457

Ответ: 113.

Причём даже если брать любые цифры от 1 до 9: сумма ${1..9}56 и ${1..9}57 неизменно 113. Это работает с любыми цифрами — сумма, конечно же, другая, но первый разряд числа с долларом будто исчезает при вычислении. А куда исчезает? Кто знает?

Однако, с нулём такой фокус не проходит:

expr $056 + $057

Выводит ошибку: expr: non-integer argument

Почему так происходит?

Попутно выяснил, что умножение при помощи expr делается именно через обратный слэш:

expr 5 * 3 

— выдаст ошибку. А вот так:

expr 5 \* 3

— посчитает нормально.

Кто столкнулся с непонятным поведением expr при вычислениях, вот тут (linux.org.ru) немного прояснили, откуда чего берётся, и что нужно делать, чтобы вычислялось нормально. Вкратце: нужно экранировать арифметические операторы.

👍

Последнее исправление: Desmond_Hume (всего исправлений: 8)
Ответ на: комментарий от urxvt

Я правильно понимаю, что, например, $5 у нас никак не обозначена, поэтому для bash это 0? Поэтому он и считает, будто на первом месте числа нет никакого значения?

Проверил это предположение, но выходит так:

$ 5=test
5=test: command not found
$ 5=5
5=5: command not found
Desmond_Hume 👍
() автор топика
$ cat expr12
#!/bin/sh

expr $11 + $22
[yury@smaug:2:2 projects]$ ./expr12
3
[yury@smaug:2:2 projects]$ ./expr12 1 2
33
[yury@smaug:2:2 projects]$ ./expr12 3 4
73

А что собственно тут ожидалось? Сумма в баксах?

argv_0_
()
Ответ на: комментарий от Desmond_Hume

Я правильно понимаю, что, например, $5 у нас никак не обозначена, поэтому для bash это 0? Поэтому он и считает, будто на первом месте числа нет никакого значения?

Почти. Если переменная не определена то возвращается пустая строка а не 0.

Проверил это предположение, но выходит так:

Ха, а в zsh это работает.

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

Хорошо, а вот ещё странность: expr 5 * 3 и expr 5 \ 3 возвращают ошибку.

Выглядит так:

$ a=5
$ b=3
$ expr $a * $b
expr: syntax error: unexpected argument ‘♫ [1988] Rain Man •  Hans Zimmer ▬ № 01 - ''Drive From The Country'' [WxXuWTDydnI].mp3’
$ expr $a \ $b
expr: syntax error: unexpected argument ‘ 3’
$ 

Без переменных ошибка та же.

В man написано так:

 ARG1 * ARG2
              arithmetic product of ARG1 and ARG2
Desmond_Hume 👍
() автор топика
Ответ на: комментарий от Desmond_Hume

Но то, что он считает, не выдавая ошибку, странновато.

Потому что ошибки нет. Shell раскрывает переменные и wildcards, а потом получившееся передаёт как аргументы команде. Это очень удобно использовать в скриптах.

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

Вот такие странности происходят иногда при вычислениях в Bash, будьте готовы, неофиты баша:

$ expr 5 * 3
expr: syntax error: unexpected argument ‘♫ [1988] Rain Man •  Hans Zimmer ▬ № 01 - ''Drive From The Country'' [WxXuWTDydnI].mp3’
$ expr 5 \* 3
15
$ expr 15 / 3
5
$ expr 15 \/ 3
5

Почему нужно экранировать знак умножения в программе-вычислителе, а знак деления не нужно, но можно и заэкранировать, ошибку не выдаст - непонятно … методом проб и ошибок дошли до истины в вычислениях).

А вот ещё на закуску:

$ expr 15 + 3
18
$ expr 15 \+ 3
18
$ expr 15 - 3
12
$ expr 15 \- 3
12

Об экранировании в man-страничке не написано ни слова (по крайней мере, явно).

Desmond_Hume 👍
() автор топика
Последнее исправление: Desmond_Hume (всего исправлений: 2)
Ответ на: комментарий от Desmond_Hume

Получается, что в man-странице информация недостоверная.

Страница руководства expr описывает аргументы expr, но не то, что может произойти до их передачи команде. Последнее описывает страница руководства используемой оболочки.

Evenik
()

Это не баг, у тебя переменные от $1 до $9 заданы пустыми строками, а $0 — именем исполняемого скрипта. Ты бы хоть просто echo сдедал вместо expr — понятнее было бы.

Попутно выяснил, что умножение делается именно через обратный слэш:

Ну надо же! Оказывается, шелл раскрывает звёздочку в файлы с любым именем.

Сколько нам открытий чудных…

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

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

Ну надо же! Оказывается, шелл раскрывает звёздочку в файлы с любым именем.

Дело в том, что это происходит в аргументах expr. Причём тут wildcards? Приведи пример, где expr будет * считать маской.

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

у тебя переменные от $1 до $9 заданы пустыми строкам

У меня они не заданы. И задать их чем-то ещё не получится. Попробуй сам присвоить значение переменной «5» например. У меня не получилось. Делал примерно так: 5=3, 5=test1

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

Дезмонд, женись уже на Пенелопе, а программирование оставь.

У меня они не заданы

Незаданные переменные равны пустым строкам.

И задать их чем-то ещё не получится

Они зарезервированы как позиционные параметры.

Приведи пример, где expr будет * считать маской

Шелл не ИИ, он не думает за тебя. Оболочка обрабатывает некоторые последовательности до передачи команде. Если нужно передать без обработки, используй экранирование или одинарные кавычки.

Evenik
()
Ответ на: комментарий от Desmond_Hume

Почему нужно экранировать знак умножения в программе-вычислителе

Ты экранируешь в программе-интерпретаторе до того, как команда дойдёт до программы-вычислителя

Вот ещё один сюрприз:

$ expr 5 '*' 3
15
grazor
()
Последнее исправление: grazor (всего исправлений: 1)
Ответ на: комментарий от Evenik

Незаданные переменные равны пустым строкам.

Почему тогда не выдаётся ошибка о том, что это non-integer значение?

Шелл не ИИ, он не думает за тебя. Оболочка обрабатывает некоторые последовательности до передачи команде. Если нужно передать без обработки, используй экранирование или одинарные кавычки.

$ expr '5' '*' '4'
20
$ expr 5 '*' 4
20

Вот так работает, да. Но экранирование - это один знак, печатать быстрее.

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

Ты экранируешь в программе-интерпретаторе до того, как команда дойдёт до программы-вычислителя

Вот теперь дошло. Похоже на правду. Но! Есть одно маленькое «но»…почему же тогда интерпретатор не выполняет запуск сначала expr? Обработка ведь идёт слева-направо, но не наоборот. Сначала bash получает команду запустить expr, а дальше передаёт «управление» expr …

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

Опять ты за своё. Я ж тебе уже объяснял когда ты чушь про cut писал. expr (/usr/bin/expr) это программа, bash это командная оболочка, между ними нет никакой связи кроме той что ты эту прогу вызваешь из баша.

$ \ и * - это спецсимволы баша, он их превращает в что-то другое перед тем как отправить в expr, поэтому у тебя всё и ломается. Заключай каждый аргумент в одинарные кавычки, обратный слеш (\) не используй, и всё будет работать.

firkax ☕☕☕☕☕
()
Ответ на: комментарий от firkax

обратный слеш () не используй

Почему не использовать? Если работает так, как ожидаешь - разве это неправильно? Он умножает. \ - набрать быстрее, чем два раза клацнуть на кавычках.

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

Почему тогда не выдаётся ошибка о том, что это non-integer значение?

Потому что $123 баш парсит как $1"23" - $1 заменяется на пустую строку, 23 остаётся. Кстати я не знал об этом, думал что оно посчитается за аргумент номер 123. А съедать только первую цифру это неочевидно и опасно багами в скриптах. Но что есть, то есть.

firkax ☕☕☕☕☕
()
Ответ на: комментарий от Desmond_Hume

Умножает не слэш а звёздочка. Слэш это команда башу не портить звёздочку.

\ - набрать быстрее, чем два раза клацнуть на кавычках.

Ну, твоё дело, по-моему кавычки нагляднее.

firkax ☕☕☕☕☕
()
Ответ на: комментарий от firkax

баш парсит как $1"23" - $1 заменяется на пустую строку, 23 остаётся.

С такой логикой получается следующее - expr $123 \* $122 :

  1. Пустая строка

  2. 23

  3. * - умножить (никого нет, идём дальше)

  4. Пустая строка

  5. 22

И что будет? 22 в итоге? Не сходится …

Desmond_Hume 👍
() автор топика
Последнее исправление: Desmond_Hume (всего исправлений: 2)
Ответ на: комментарий от firkax

Умножает не слэш а звёздочка. Слэш это команда башу не портить звёздочку.

Не бери пример с других)). Где я утверждал, что именно слэш умножает? Мне ахинею написал ИИ, я решил его проверить (ну, бывает, вдруг я чего-то не знаю, что сумел узнать ИИ) …

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

Дело в том, что это происходит в аргументах expr. Причём тут wildcards? Приведи пример, где expr будет * считать маской.

Нет, это не происходит в аргументах expr, это происходит до того, как expr вообще о чём-то узнает. В любой команде сначала раскрываются wildcards и переменные, а потом уже получается команда и её аргументы, соответственно. Экранировать модно и не через \, а беря в одинарные или двойные кавчки (в двойнух перемменные раскрываются, wildcards нетб в одинарных всё как есть остаётся). Собственно для этого в шелле и есть экранировани и кавычки.

У меня они не заданы. И задать их чем-то ещё не получится. Попробуй сам присвоить значение переменной «5» например. У меня не получилось. Делал примерно так: 5=3, 5=test1

Заданы. Зачем мне пробовать, ведь я читал мануал и знаю, что я не могу присвоить им значения. Это аргументы командной строки для скрипта, который ты пишешь.

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

Но если чтение религия запрещает и надо тыкаться, то напиши скрипт:

#!/bin/sh
echo '$1' = $1
echo '$2 =' $2
echo '$3' = $3
echo '$4' = $4
echo '$5' = $5
echo '$6' = $6
echo '$7' = $7
echo '$8' = $8

И позапускай его с разными аргументами.

Например ./scriptname.sh Сорок тысяч Обезьян 'в жопу' сунули банан.

поймёшь, как работает

P.S. желающим прокричать про «кошмар, можно же циклом, зачем столько копипасты!!!» — для наглядности, в целях обучения

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

Нет, не слева направо. Башу сначала нужно понять, какие аргументы передавать команде

$ echo $(pwd)
/home/grazor
$ echo '$(pwd)'
$(pwd)

В первом случае баш не передаст в echo аргумент $(pwd). echo не знает, что это такое и обрабатывает входные аргументы только как строки. Баш сначала сам по своим правилам обработает аргумент и сдалет из него /home/grazor, а потом передаст эту строку в echo. Иначе бы одну и ту же логику по обработке аргументов пришлось бы дублировать в каждой утилите

И * без экранирования тут для баша такой же параметр, который сначала нужно вычислить как список файлов по указанному пути (в данном случае — в текущей директории)

Иначе бы баш был бесполезным и топорным, не давал бы ничего сделать кроме банального вызова команд

bash -xc 'echo $(pwd)'
++ pwd
+ echo /home/grazor
/home/grazor
$ bash -xc 'expr 5 \* 3'
+ expr 5 '*' 3
15

$ bash -xc 'expr 5 * 3'
+ expr 5 file1 file2 file3 3
expr: syntax error: unexpected argument ‘file1’

grazor
()
Ответ на: комментарий от firkax

\n - можно представить пустой строкой, грубо говоря? \n5 \* \n5 чему будет равно? Как можно конкатенировать пустую строку с числом, я не понимаю? Разве такое возможно?

Desmond_Hume 👍
() автор топика
Ответ на: комментарий от grazor

Нет, не слева направо. Башу сначала нужно понять, какие аргументы передавать команде

$ echo $(pwd)
/home/grazor
$ echo '$(pwd)'
$(pwd)

Вот как раз этот пример доказывает мою логику: сначала интерпретатор запускает программу, которую скомандовали, а дальше программа начинает «управлять» …

Грубо говоря, происходит следующее:

  • запускаем программу
  • программа запустилась и смотрит «чё тут?» - о, какие-то аргументы! ну-ка, сейчас попробую сделать что-то с ними … эй, баш, тут какая-то ерунда написана, не могу вычислить!
Desmond_Hume 👍
() автор топика
Последнее исправление: Desmond_Hume (всего исправлений: 1)
Ответ на: комментарий от CrX

Я понял, что ты мастер обезьянно-бонановых дел, но ты так и не показал пример, где можно присвоить переменной 5 значение «банановый мастер», чтобы я мог выполнить команду echo $5 - и получить вывод «банановый мастер». Пробуй, добейся … реально интересно стало, как такое возможно. Моя парадигма о переменных точно поломается, если сможешь доказать такое.

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

но ты так и не показал пример, где можно присвоить переменной 5 значение «банановый мастер»

Например ./scriptname.sh Сорок тысяч Обезьян ‘в жопу’ сунули банан.

Тебе показали пример, где $5 присвоили значение «cунули»
$0=«./scriptname.sh»
$1=«Сорок»
$2=«тысяч»
и т.д.

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

Вот (linux.org.ru) пример. Явное указание переменной. Присваиваем значение переменной. Аргумент это не переменная. Переменная может быть аргументом - да, но наоборот нет. Я - человек! Человек - потому что я? Нет. Я - переменная «человеку». Человек Вася, Петя и т.д. Но Вася, Петя и я - не обязательно люди. Могут быть коты, собаки, квадроберы и проч. с такими «переменными». Аргумент - человек. «Вася» - переменная.

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

Вообще-то я этот пример привёл! Ты даже не попробовал этот скрипт написать и запустить с соответствующими аргументами.

Ещё раз: в переменные от $1 до $9 попадают аргументы, переданные команде. То есть, твоему скрипту. Соответственно, «присвоить» их можно только во время запуска скрипта, но не внутри него. Вот тебе скрипт, в котором командой echo $5 будет выведено «банановый мастер»:

#!/bin/sh
echo $5

Ага, вот так просто. Но есть нюанс. Запускать скрипт надо так: ./scriptname 1 2 3 4 'банановый мастер'.

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

В бонановых фантазиях — да, всё подтверждается. В реальной жизни так:

$ $1="Сорок"
=Сорок: command not found
$ echo $Сорок
$Сорок
$ 
$ $1="Сорок"
=Сорок: command not found
$ echo $$1
1056171
$ echo $1

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

См. сообщение над тобой. Я понял, ты на своей волне. Приведи пример явного указания переменных, а не через специальные значения аргументов.

Ты все еще говоришь про bash? В нем невозможно определить переменные пользователя с именами $N - они зарезервированы за башем и имеют специальное значение.

sigurd
()