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 при вычислениях, вот тут немного прояснили, откуда чего берётся, и что нужно делать, чтобы вычислялось нормально. Вкратце: нужно экранировать арифметические операторы.

★★★★★

Последнее исправление: 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 ★★★★★
() автор топика
Ответ на: комментарий от Evenik

Мегамозг oldGPT выдал именно такой синтаксис. Я его проверил, на всякий случай. Сам удивился. Всегда же умножение было звёздочкой, а деление слэшем …

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)
Ответ на: комментарий от grazor

А теперь не через функцию. echo $5 Давай попробуем вот так:

5="банановый"
3="чемпион"
echo $5$3

Если у тебя получится, признаю сразу тебя бонановым чемпионом.

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

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

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

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

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

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

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

явное указание переменной.

Кончай бредить!
У баша есть предопределенные переменные $N, начиная с $0 - имя скрипта. Их нельзя использовать иначе.

sigurd ★★★★★
()
Ответ на: комментарий от 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 ★★★★★
()