Во время отладки стороннего Tcl-скрипта я столкнулся с очень странным поведением: когда скрипт распаковывает существующий файл с помощью gunzip, Tcl 'file exists' отрицает существование файла результата, даже если он виден через 'ls' из shell и даже через Tcl 'glob'. Мне удалось воспроизвести поведение в этом коротком (ish) скрипте:

#!/usr/bin/tclsh

proc assert {msg asserted} {
  if {!$asserted} {
    puts "Assertion failed: $msg"
    exit 1
  }
}

set script_path [info script]
set test_file [file join [file dirname $script_path] "test.tcl.gz"]
puts "The test file is '$test_file'"
assert "The test file does not exist" [file exists $test_file]

set this_dir [pwd]
set test_dir [file join $this_dir "test"]
if {![file exist $test_dir]} {
  file mkdir $test_dir
}
puts "The test directory is '$test_dir'"
assert "'$test_dir' does not exist or is not a directory" [file isdirectory $test_dir]

file copy $test_file $test_dir
set working_file [file join $test_dir [file tail $test_file]]
assert "Failed to to copy the test file to '$working_file'" [file exists $working_file]

exec gunzip $working_file
puts "After decompression, $test_dir contains:"
foreach file [glob -dir $test_dir *] {
  puts $file
}
set decompressed_file [file rootname $working_file]
assert "$decompressed_file does not exist" [file exists $decompressed_file]

Тестовый файл, который я использую, представляет собой просто сжатую копию исходного кода скрипта. Он находится в том же каталоге, что и сам сценарий, как ожидает и проверяет сценарий. Вот что я получаю, когда запускаю его (tcl 8.5 / CentOS 6.5):

bash-4.1$ ./test.tcl 
The test file is './test.tcl.gz'
The test directory is '/home/jbolling/tmp/test'
After decompression, /home/jbolling/tmp/test contains:
/home/jbolling/tmp/test/test.tcl
Assertion failed: /home/jbolling/tmp/test/test.tcl does not exist

Кто-нибудь может объяснить такое поведение? Есть ли способ убедить, что «файл существует», чтобы распознать распакованный файл?

tcl
0
John Bollinger 3 Июл 2014 в 23:37
Я не получаю сообщение об ошибке утверждения при запуске вашего скрипта. Вы можете сделать ls -ld . test test/test.tcl.
 – 
Brad Lanam
4 Июл 2014 в 04:27
Я также не вижу провала утверждения. Моя среда: Mac OS X 10.9.x, Tcl 8.5.9.
 – 
Hai Vu
4 Июл 2014 в 08:10
Команда file exists в значительной степени соответствует системному вызову access() с флагом F_OK во всех Unix, включая OSX; Windows сложнее из-за различий в моделях блокировки файлов. На какой платформе вы находитесь?
 – 
Donal Fellows
4 Июл 2014 в 16:12
Ланам: 'ls' показывает существующий файл, как и 'glob'. Рабочий каталог (.=/home/jbolling/tmp) имеет права доступа 0750, подкаталог test имеет права 0755, а test/test.tcl имеет права 0644.
 – 
John Bollinger
7 Июл 2014 в 19:28
Товарищи: это CentOS 6.5 (Linux).
 – 
John Bollinger
7 Июл 2014 в 19:30

3 ответа

Лучший ответ

Узнав из комментариев других, что исходный сценарий работал на них, обнаружив некоторые варианты, которые также работали для меня, и ковыряясь в исходном коде Tcl, я пришел к выводу: однажды использовав file exists на объект, небезопасно использовать его снова на этом объекте или на объекте, производном от него через file rootname, если содержимое каталога могло измениться между вызовами (что часто нельзя исключать).

Я не проследил, где это происходит в источнике, но мой интерпретатор Tcl явно где-то кэширует результаты списка каталогов, поскольку он не только не распознает создание распакованного файла, но также не распознает удаление оригинала (этот же архиватор также выполняет). С другой стороны, если я выполняю тест file exists, используя буквальный строковый путь или используя объект Tcl, который достаточно отделен от любого ранее переданного в file exists, то результат будет таким, как ожидалось. Это также работает, если я принудительно преобразовываю в строку, например, заменяя

assert "$decompressed_file does not exist" [file exists $decompressed_file]

С участием

assert "$decompressed_file does not exist" [file exists [string trim $decompressed_file]]

Очевидно, поведение кэширования зависит от реализации; моя реализация - Tcl 8.5.7 из CentOS 6.5, но я полагаю, что поведение, вероятно, является общим, по крайней мере, для реализации Tcl 8.5.7 для Unix (которая отличается от реализации macosx, хотя OS X - это Unix). Также может быть, что gunzip делает что-то необычное, что усугубляет проблему, потому что мне не удалось продублировать это с помощью exec простой команды mv.

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

0
John Bollinger 8 Июл 2014 в 01:38

Незначительная проблема, но здесь похоже на возможную опечатку ...

if {![file exist $test_dir]} {

Должно быть:

if {![file exists $test_dir]} {
0
HaveNoDisplayName 23 Мар 2015 в 18:17
Спасибо за ваш комментарий. (1) Вы правы в том, что в опубликованном источнике есть опечатка, но на самом деле это никак не влияет на поведение скрипта (как ни странно). Сценарий работает точно так же, как первоначально опубликовано, и он также работает с исправленной опечаткой, что дает точно такие же изменения файловой системы и вывод в любом случае. (2) Это было бы гораздо более уместно в качестве комментария к исходному вопросу, чем в качестве ответа, поскольку на самом деле это не ответ на вопрос.
 – 
John Bollinger
23 Мар 2015 в 18:09

В руководстве для файл существует указано, что команда

Возвращает 1, если имя файла существует и текущий пользователь имеет права поиска для каталогов, ведущих к нему, 0 в противном случае.

Очевидно, что файл существует, поэтому я подозреваю, что пользователь, у которого вы запускаете скрипт, не имеет разрешения на его просмотр. Должен признаться, я не совсем уверен, что означает «привилегия поиска для каталогов, ведущих к нему».

Я предлагаю повторить попытку с файлом, находящимся в общедоступном каталоге.

0
TrojanName 4 Июл 2014 в 01:51
«Права поиска» для /a/b/c/d/file.txt означают, что вы должны иметь возможность перейти в /a, /a/b, /a/b/c ... и так далее, вплоть до /a/b/c/d
 – 
Hai Vu
4 Июл 2014 в 08:08
Сценарий создает целевой файл, сначала копируя сжатую версию в целевой каталог, а затем распаковывая ее. Он работает как я, и у меня, безусловно, есть права на поиск во всех каталогах на пути к нему (подкаталог моего домашнего каталога), которые я могу проверить вне сценария.
 – 
John Bollinger
7 Июл 2014 в 19:34
Кроме того, сценарий успешно использует file exists для проверки существования .gz в целевом каталоге перед распаковкой, поэтому проблема не может быть объяснена отсутствием прав поиска в этом каталоге.
 – 
John Bollinger
7 Июл 2014 в 19:41
Очень странно. Интересно, это артефакт того, что вы запускаете его как сценарий оболочки. Не могли бы вы открыть оболочку TCL и вставить код прямо в нее и посмотреть, отличается ли она?
 – 
TrojanName
7 Июл 2014 в 23:18
Я открыл интерактивный tclsh и вставил в него свой оригинальный скрипт (выше). Это дало мне тот же результат "glob" и ту же непоследовательную ошибку утверждения. Это была интересная мысль, но не кости.
 – 
John Bollinger
8 Июл 2014 в 00:14