не работает скрипт (не пойму почему обнуляются переменные)
Модераторы: /dev/random, Модераторы разделов
-
- Сообщения: 35
- ОС: Ubuntu
не работает скрипт
в цикле while с 41 строки по 187 переменные
LASTLINE
LASTLINE_V2
HEADERLINENUM
HEADERLINENUM_V2
имеют значение, но на 189-й сразу равны нулю. почему? ведь верхний цикл for должен выполнятся 1 раз на 1 файл, т.е. строчки которые были до while в цикле for должны выполнится 1 раз. разве не так?
LASTLINE
LASTLINE_V2
HEADERLINENUM
HEADERLINENUM_V2
имеют значение, но на 189-й сразу равны нулю. почему? ведь верхний цикл for должен выполнятся 1 раз на 1 файл, т.е. строчки которые были до while в цикле for должны выполнится 1 раз. разве не так?
У вас нет необходимых прав для просмотра вложений в этом сообщении.
-
- Сообщения: 1147
- Статус: Slacker!
- ОС: Slackware64-current
Re: не работает скрипт
Выложите пожалуйста, tar, сжатый одним из компрессоров: gz, bzip2, xz. RAR в линуксе - не "родной" архив, поэтому у некоторых могут быть проблемы с распаковкой вашего файла.
Slackware64-current/Xfce/Xiaomi Mi Notebook Pro 15.6 | Arch Linux/Xfce/Lenovo G580
-------------
Registered Linux User #557010
-------------
Registered Linux User #557010
-
- Сообщения: 35
- ОС: Ubuntu
-
- Администратор
- Сообщения: 5405
- ОС: Gentoo
Re: не работает скрипт
Вы выложили несжатый. Для 20-килобайтного файла это особого значения не имеет, но на будущее, не забывайте про сжатие.
Ошибка у вас в следующем. В большинстве оболочек, включая bash, команды, входящие в пайплайн, выполняются в подоболочках. Цикл у вас - последняя команда пайплайна. Переменные, само собой, за пределы подоболочки выйти не могут.
Если вы используете bash, то можете указать "shopt -s lastpipe", что заставит последнюю команду каждого пайплайна выполняться без подоболочки, но в этом случае в других оболочках ваш скрипт выполняться не сможет. Единственный способ решить проблему универсальным, не зависящим от оболочки образом - сохранять вывод левой команды во временный файл или переменную и затем обрабатывать этот файл правой командой, без пайпов.
Upd: исправил опечатку.
-
- Сообщения: 35
- ОС: Ubuntu
Re: не работает скрипт
/dev/random писал(а): ↑22.11.2012 19:09
Вы выложили несжатый. Для 20-килобайтного файла это особого значения не имеет, но на будущее, не забывайте про сжатие.
Ошибка у вас в следующем. В большинстве оболочек, включая bash, команды, входящие в пайплайн, выполняются в подоболочках. Цикл у вас - последняя команда пайплайна. Переменные, само собой, за пределы подоболочки выйти не могут.
Если вы используете bash, то можете указать "shopt -s subpipe", что заставит последнюю команду каждого пайплайна выполняться без подоболочки, но в этом случае в других оболочках ваш скрипт выполняться не сможет. Единственный способ решить проблему универсальным, не зависящим от оболочки образом - сохранять вывод левой команды во временный файл или переменную и затем обрабатывать этот файл правой командой, без пайпов.
не могли бы вы подробнее рассказать про shopt -s subpipe? в интернете ничего не нарыл =( а консоль на эту команду выдает
Код: Выделить всё
napalm@home:~/work$ shopt -s subpipe
-bash: shopt: subpipe: недопустимое имя опции оболочки
-
- Сообщения: 586
- Статус: -
Re: не работает скрипт
Как вам уже ответил /dev/random проблема в том, что ваш цикл выполняется в subshell-е. Можете попробовать использовать Process substitution для баша
или fifo для sh
В обоих случаях subshell для while создаваться не должен.
Upd1.
Оба предыдущих примера - вариант решения с файлами, предложенный /dev/random-ом. Еще один способ - выводить необходимые переменные окружения на stdout перед завершением subshell-а, и потом устанавливать их с помощью eval-а в основном окружении:
Если в значении переменной i будут кавычки, то этот способ приведет к непредсказуемым последствиям. Вот пример, который выполняет команду ls и устанавливает _две_ переменных вместо предполагаемой одной с помощью специально подобранного значения переменной i:
И, наконец, еще один способ: просто не использовать конструкцию ' | while read'. Если файл небольшой, можно его содержимое подставить через command substitution "напрямую":
Для файла в 50мб самым быстрым оказался последний способ, если выполнять в dash:
(обратите внимание, что разница между временем для ./t4.sh в dash и bash отличается больше, чем в 9 раз)
И еще интересный вопрос - почему у этих скриптов отличаются ответы
(если что, я понятия не имею, файл был бинарным).
Код: Выделить всё
$ ls -lh test.txt
-rw-r----- 1 sgf sgf 50M 11\346\234\210 22 22:33 test.txt
$ cat ./t.sh
#!/bin/bash
i=1
while read line; do
i=$((i + 1))
done < <(cat 'test.txt')
echo "$i"
$ bash ./t.sh
187758
или fifo для sh
Код: Выделить всё
$ cat t2.sh
#!/bin/sh
i=1
rm -f 1.tmp
mkfifo 1.tmp
cat 'test.txt' > 1.tmp &
while read line; do
i=$((i + 1))
done < ./1.tmp
rm 1.tmp
echo "$i"
$ sh ./t2.sh
187752
В обоих случаях subshell для while создаваться не должен.
Upd1.
Оба предыдущих примера - вариант решения с файлами, предложенный /dev/random-ом. Еще один способ - выводить необходимые переменные окружения на stdout перед завершением subshell-а, и потом устанавливать их с помощью eval-а в основном окружении:
Код: Выделить всё
$ cat ./t3.sh
#!/bin/sh
cycle()
{
while [ 0 ]; do
if ! read line; then
echo "i=\"$i\""
break
fi
i=$((i + 1))
done
}
i=1
eval "$(cat 'test.txt' | cycle)"
echo "$i"
$ sh ./t3.sh
187752
Если в значении переменной i будут кавычки, то этот способ приведет к непредсказуемым последствиям. Вот пример, который выполняет команду ls и устанавливает _две_ переменных вместо предполагаемой одной с помощью специально подобранного значения переменной i:
Код: Выделить всё
$ ( eval "$( i='1"; ls; v="2'; echo i="\"$i\""; )"; echo "i=$i, v=$v" )
fsdf fsdf.csv report_v.2.0.sh t2.sh t3.sh t4.sh test.txt t.sh work.rar work.tar
i=1, v=2
И, наконец, еще один способ: просто не использовать конструкцию ' | while read'. Если файл небольшой, можно его содержимое подставить через command substitution "напрямую":
Код: Выделить всё
$ cat ./t4.sh
#!/bin/sh
nl='
'
set -f
IFS="$nl"
for line in $(cat 'test.txt'); do
i=$((i + 1))
done
echo "$i"
Для файла в 50мб самым быстрым оказался последний способ, если выполнять в dash:
Код: Выделить всё
$ ll -h test.txt
-rw-r----- 1 sgf sgf 50M 11\346\234\210 22 22:33 test.txt
$ time bash ./t.sh
187758
real 0m32.312s
user 0m11.493s
sys 0m20.877s
$ time sh ./t2.sh
rm: cannot remove `1.tmp': No such file or directory
187752
real 0m24.954s
user 0m4.680s
sys 0m20.329s
$ time sh ./t3.sh
187752
real 0m25.377s
user 0m4.688s
sys 0m20.645s
$ time sh ./t4.sh
187879
real 0m1.273s
user 0m1.040s
sys 0m0.304s
$ time bash ./t4.sh
187879
real 0m18.947s
user 0m18.501s
sys 0m0.460s
(обратите внимание, что разница между временем для ./t4.sh в dash и bash отличается больше, чем в 9 раз)
И еще интересный вопрос - почему у этих скриптов отличаются ответы

-
- Администратор
- Сообщения: 5405
- ОС: Gentoo
-
- Сообщения: 8735
- Статус: GPG ID: 4DFBD1D6 дом горит, козёл не видит...
- ОС: Slackware-current
Re: не работает скрипт
вот-вот... А мне тут ещё рассказывают, что конструкция cat file| while read; do; done имеет право на существование...
в данном случае я-бы разорился на временный файл, который-бы и отправил в цикл.
Спасибо сказали:
-
- Сообщения: 35
- ОС: Ubuntu
Re: не работает скрипт
-
- Сообщения: 355
- ОС: Gentoo
Re: не работает скрипт
Не‐а. Ещё можно загнать весь (то есть, оба конца) pipe в VAR="$(…)" и обрабатывать $VAR. Для того, чтобы команды могли использовать stdout есть/dev/random писал(а): ↑22.11.2012 19:09Единственный способ решить проблему универсальным, не зависящим от оболочки образом - сохранять вывод левой команды во временный файл или переменную и затем обрабатывать этот файл правой командой, без пайпов.
(Created by format.vim)
exec 3>&1
VAR="$(echo Message >&3)"
. Работает в dash и bb, значит, скорее всего, является стандартным.
-
- Администратор
- Сообщения: 5405
- ОС: Gentoo
Re: не работает скрипт
ZyX писал(а): ↑31.12.2012 21:51Не‐а. Ещё можно загнать весь (то есть, оба конца) pipe в VAR="$(…)" и обрабатывать $VAR. Для того, чтобы команды могли использовать stdout есть(Created by format.vim)
exec 3>&1 VAR="$(echo Message >&3)"
. Работает в dash и bb, значит, скорее всего, является стандартным.
И как вы предлагаете это применить к проблеме топикстартера? Ему нужно [было месяц назад], чтобы куча переменных, получающих значения в цикле, который является последней частью пайпа, сохраняла эти значения после цикла. Вы предлагаете обе части, т.е. и цикл тоже, вставить в subshell substitution. И чем это поможет переменным, получившим значения внутри цикла?
-
- Сообщения: 355
- ОС: Gentoo
Re: не работает скрипт
Выдаёте результат, для которого можно делать eval (например, просто set, если не смущает количество лишних переменных), делаете eval результата, в чём проблемы? Это просто общий метод получения значений из pipe. Нет никакой разницы, получать ли оттуда значение одной переменной или всех сразу./dev/random писал(а): ↑31.12.2012 22:10ZyX писал(а): ↑31.12.2012 21:51Не‐а. Ещё можно загнать весь (то есть, оба конца) pipe в VAR="$(…)" и обрабатывать $VAR. Для того, чтобы команды могли использовать stdout есть(Created by format.vim)
exec 3>&1 VAR="$(echo Message >&3)"
. Работает в dash и bb, значит, скорее всего, является стандартным.
И как вы предлагаете это применить к проблеме топикстартера? Ему нужно [было месяц назад], чтобы куча переменных, получающих значения в цикле, который является последней частью пайпа, сохраняла эти значения после цикла. Вы предлагаете обе части, т.е. и цикл тоже, вставить в subshell substitution. И чем это поможет переменным, получившим значения внутри цикла?
-
- Сообщения: 8735
- Статус: GPG ID: 4DFBD1D6 дом горит, козёл не видит...
- ОС: Slackware-current
Re: не работает скрипт
eval штука опасная, и обычно можно и без неё обойтись. Например не нужно пайп пихать в цикл.