texify 可以用来生成多个书目吗?

texify 可以用来生成多个书目吗?

我正在使用 MikTex texify 编译器驱动程序,并且我有一个使用 multibib 包的文档。pdflatex 编译生成main.aux(匹配main.tex) 和own.aux(使用 multibib 生成)。但是,Texify 只会运行 bibtex,main.aux因此某些参考文献将无法正确更新。我一直在使用一个简单的 shell 脚本:

bibtex main
bibtex own

每次我引用新的参考文献时都需要运行它。有没有办法让 texify 运行 bibtex 两次,或者复制 texify 正在做的事情并制作一个执行整个排版过程的脚本?

答案1

我查看了 texify 的源代码,文件mcd.cpp函数Driver::Ready()。它查找文件"Rerun to get"中的出现情况.log以及辅助文件中的更改,以查看是否应该再次运行 latex。在 Unix 环境中编写脚本非常简单,但由于我在 Windows 中,因此我将以下内容放在一起:

#include <vector>
#include <string>
#include <algorithm>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
#include <stdarg.h>
#include <ctype.h>
#if defined(_WIN32) || defined(_WIN64)
#define NOMINMAX
#include <windows.h>
#else // _WIN32 || _WIN64
// ...
#endif // _WIN32 || _WIN64

#define SHA1HashSize 20

/*
 *  This structure will hold context information for the SHA-1
 *  hashing operation
 */
typedef struct SHA1Context
{
    uint32_t Intermediate_Hash[SHA1HashSize/4]; /* Message Digest  */

    uint32_t Length_Low;            /* Message length in bits      */
    uint32_t Length_High;           /* Message length in bits      */

                               /* Index into message block array   */
    int_least16_t Message_Block_Index;
    uint8_t Message_Block[64];      /* 512-bit message blocks      */

    int Computed;               /* Is the digest computed?         */
    int Corrupted;             /* Is the message digest corrupted? */
} SHA1Context;

/*
 *  Function Prototypes
 */
int SHA1Reset(SHA1Context*);
int SHA1Input(SHA1Context*, const void*, unsigned int);
int SHA1Result(SHA1Context*, uint8_t Message_Digest[SHA1HashSize]);

void Format(std::string &r_s_result, const char *p_s_fmt, ...) // throw(std::bad_alloc)
{
    va_list ap;

    size_t n_size = strlen(p_s_fmt) + 1;
    r_s_result.resize(n_size);
    // alloc str as long as formatting str (should be enough in some cases)

    for(;;) {
        va_start(ap, p_s_fmt);
#if defined(_MSC_VER) && !defined(__MWERKS__)
#if _MSC_VER >= 1400
        int n = _vsnprintf_s(&r_s_result[0], n_size * sizeof(char), _TRUNCATE, p_s_fmt, ap);
#else // _MSC_VER >= 1400
        int n = _vsnprintf(&r_s_result[0], n_size - 1, p_s_fmt, ap);
        // maximum characters to write, not the size of buffer
#endif // _MSC_VER >= 1400
#else // _MSC_VER && !__MWERKS__
        int n = vsnprintf(&r_s_result[0], n_size * sizeof(char), p_s_fmt, ap);
#endif // _MSC_VER && !__MWERKS__
        va_end(ap);
        // try to sprintf

        if(n >= 0 && unsigned(n) < n_size) {
#if defined(_MSC_VER) && !defined(__MWERKS__)
            r_s_result.resize(n);
#else // _MSC_VER && !__MWERKS__
            r_s_result.resize(strlen(r_s_result.data())); // doesn't need c_str(), terminating zero is already there
#endif // _MSC_VER && !__MWERKS__
            assert(r_s_result.length() == strlen(r_s_result.data()));
            return;
        }
        // see if we made it

        if(n >= 0) // glibc 2.1
            n_size = n + 1; // precisely what is needed
        else // glibc 2.0, msvc
            n_size *= 2;

        r_s_result.resize(n_size);
        // realloc to try again
    }
}

/**
 *  @brief gets temporary directory the user can write to
 *  @param[out] r_s_path is the path to the temporary directory, never ends with a slash
 *  @return Returns true on success, false on failure.
 */
bool Get_TempDirectory(std::string &r_s_path) // throw(std::bad_alloc)
{
#if defined(_WIN32) || defined(_WIN64)
    r_s_path.resize(GetTempPath(0, NULL) + 1);
    if(!GetTempPathA((DWORD)r_s_path.size(), &r_s_path[0])) // size won't exceed DWORD_MAX, since it's read from DWORD
        return false; // something went wrong
    assert(strlen(r_s_path.c_str()) > 0 && r_s_path[strlen(r_s_path.c_str()) - 1] == '\\'); // the returned string ends with a backslash
    r_s_path.resize(strlen(r_s_path.c_str()) - 1); // cut the backslash here
    // get temp path (eg. "c:\\windows\\temp")
#else // _WIN32 || _WIN64
#if 0 // g++ linker warns about using mktemp(), don't want to use that anymore
    char p_s_temp[256] = "/tmp/fileXXXXXX";
    if(!mktemp(p_s_temp)) // do *not* use mkstemp(), do not want the file to be lying around
        return false;
    assert(strrchr(p_s_temp + 1, '/')); // should contain slash
    *(char*)strrchr(p_s_temp + 1, '/') = 0; // erase the last slash (hence the string does not contain it)
    r_s_path = p_s_temp;
    // get temp file name and erase the file name to get the path
#else // 0
    const char *p_s_temp = getenv("TMPDIR"); // environment variable
    // The caller must take care not to modify this string, since that would change the
    // environment of the process. Do not free it either.
    // (e.g. on cluster computers, temp is often directory specific to the job id, like "/tmp/pbs.132048.dm2")

    if(!p_s_temp) {
        if(P_tmpdir)
            p_s_temp = P_tmpdir; // in stdio.h
        else {
            p_s_temp = "/tmp"; // just hope it is there

            /*TFileInfo t_temp_info(p_s_temp);
            if(!t_temp_info.b_exists || !t_temp_info.b_directory)
                return false;*/
            // don't want to depend on Dir.cpp, some apps already use only the header
        }
    }
    // fallbacks if the environment variable is not set

    r_s_path = p_s_temp;
    if(!r_s_path.empty() && r_s_path[r_s_path.length() - 1] == '/')
        r_s_path.erase(r_s_path.end() - 1);
    // get rid of the trailing slash
#endif // 0
#endif // _WIN32 || _WIN64

    /*assert(!b_EndsWithSlash(r_s_path));
    assert(b_Is_Normalized(r_s_path));
    assert(b_Is_Absolute(r_s_path));*/
    // make sure there is no slash at the end, and that the path is normalized

    return true;
}

bool Get_TempFileName(std::string &r_s_temp_file_name, const char *p_s_app_id) // throw(std::bad_alloc)
{
    assert(p_s_app_id && strlen(p_s_app_id));
    // may not be emtpy

#if defined(_WIN32) || defined(_WIN64)
    std::string s_temp_path;
    s_temp_path.resize(GetTempPath(0, NULL) + 1);
    if(!GetTempPathA((DWORD)s_temp_path.size(), &s_temp_path[0])) // size won't exceed DWORD_MAX, since it's read from DWORD
        return false; // something went wrong
    s_temp_path.resize(strlen(s_temp_path.c_str()));
    // get temp path (eg. "c:\windows\temp")

    r_s_temp_file_name.resize(s_temp_path.length() + 16 + strlen(p_s_app_id));
    if(!GetTempFileNameA(s_temp_path.c_str(), p_s_app_id, 0, &r_s_temp_file_name[0]))
        return false; // something went wrong
    r_s_temp_file_name.resize(strlen(r_s_temp_file_name.c_str()));
    // get temp filename
#else // _WIN32 || _WIN64
    std::string s_tmp;
    if(!Get_TempDirectory(s_tmp)) // use "proper" temp (e.g. on cluster computers, temp is often directory specific to the job id, like "/tmp/pbs.132048.dm2")
        return false;
    Format(r_s_temp_file_name, "%s/%sXXXXXX", s_tmp.c_str(), p_s_app_id); // had 8 X's // 2012-07-17 change // t_odo - carry this change to documentation
    // 2013-11-13 changed template to not include the /tmp folder as that might not be the location of tmp
    // make template

    int n_file;
    if((n_file = mkstemp((char*)r_s_temp_file_name.c_str())) < 0)
        return false;
    close(n_file);
    // create temp file
#endif // _WIN32 || _WIN64

    return true;
}

bool ReadFile(std::string &r_s_output, const char *p_s_filename) // throw(std::bad_alloc)
{
    FILE *p_fr;
#if defined(_MSC_VER) && !defined(__MWERKS__) && _MSC_VER >= 1400
    if(fopen_s(&p_fr, p_s_filename, "rb"))
#else //_MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
    if(!(p_fr = fopen(p_s_filename, "rb")))
#endif //_MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
        return false;
    unsigned int n_length;
    if(fseek(p_fr, 0, SEEK_END) ||
       (n_length = ftell(p_fr)) < 0 ||
       fseek(p_fr, 0, SEEK_SET)) {
        fclose(p_fr);
        return false;
    }
    try {
        r_s_output.resize(n_length);
    } catch(std::bad_alloc &r_exc) {
        fclose(p_fr);
        throw r_exc; // rethrow
    }
    if(fread(&r_s_output[0], sizeof(char), n_length, p_fr) != n_length) {
        fclose(p_fr);
        return false;
    }
    fclose(p_fr);
    return true;
}

void TrimSpace(std::string &r_s_string)
{
    size_t b = 0, e = r_s_string.length();
    while(e > 0 && isspace(uint8_t(r_s_string[e - 1])))
        -- e;
    while(b < e && isspace(uint8_t(r_s_string[b])))
        ++ b;
    r_s_string.erase(e);
    r_s_string.erase(0, b);
}

void Split(std::vector<std::string> &r_s_dest, const std::string &r_s_string,
    const char *p_s_separator, int n_thresh)
{
    r_s_dest.clear();
    const size_t n_separator_skip = strlen(p_s_separator);

    size_t n_pos = 0;
    size_t n_next_pos;
    while((n_next_pos = r_s_string.find(p_s_separator, n_pos)) != std::string::npos) {
        if(n_thresh < 0 || n_next_pos - n_pos > unsigned(n_thresh)) {
            r_s_dest.resize(r_s_dest.size() + 1);
            std::string &r_s_new = r_s_dest.back();
            r_s_new.insert(r_s_new.begin(), r_s_string.begin() + n_pos,
                r_s_string.begin() + n_next_pos);
        }
        n_pos = n_next_pos + n_separator_skip; // skip over the separator
    }
    if(n_thresh < 0 || r_s_string.length() - n_pos > unsigned(n_thresh)) {
        r_s_dest.resize(r_s_dest.size() + 1);
        std::string &r_s_new = r_s_dest.back();
        r_s_new.insert(r_s_new.begin(), r_s_string.begin() + n_pos,
            r_s_string.end());
    }
}

int main(int n_arg_num, const char **p_arg_list)
{
    try {
        if(n_arg_num != 2) {
            fprintf(stderr, "error: use IsTexReady <jobname>\n");
            return -1;
        }
        const char *p_s_jobname = p_arg_list[1];
        if(!strcmp(p_s_jobname, "-h") || !strcmp(p_s_jobname, "--help") ||
           !strcmp(p_s_jobname, "/?") || !strcmp(p_s_jobname, "--usage")) {
            printf("use: IsTexReady <jobname>\n\n"
                "It will return 0 in case there is no need to re-run tex,\n"
                "1 in case tex should be run once more or -1 on error.\n\n"
                "Run in the same folder where <jobname>.log can be found.\n");
            return 0; // not sure
        }
        // get jobname

        std::string s_logfile_name = std::string(p_s_jobname) + ".log";

        bool b_rerun = false;

        std::string s_logfile;
        if(!ReadFile(s_logfile, s_logfile_name.c_str())) {
            fprintf(stderr, "error: while reading \'%s\'\n", s_logfile_name.c_str());
            b_rerun = true; // should re-run as the log file might be simply missing
        }
        // read logfile

        if(s_logfile.find("Rerun to get") != std::string::npos) {
            printf("IsTexReady: should rerun latex as per the logfile\n");
            b_rerun = true;
        }
        // have "Rerun to get" something, should run again

        std::vector<std::string> aux_list;
        {
            std::string s_aux_list_name;
            if(!Get_TempFileName(s_aux_list_name, "trdy"))
                throw std::runtime_error("Get_TempFileName() failed");
            // get a temp file

#if defined(_WIN32) || defined(_WIN64)
            system(("dir /B /S *.aux *.toc *.lof *.lot *.loa *.lol *.idx > " + s_aux_list_name).c_str());
#else // _WIN32 || _WIN64
            system(("find . -name \'*.aux\' -or -name \'*.toc\' -or -name \'*.lof\' "
                "-or -name *.lot -or -name *.loa -or -name \'*.lol\' -or -name \'*.idx\' > " + s_aux_list_name).c_str());
#endif // _WIN32 || _WIN64
            // list the files in it (aux, toc, list of figures / tables / algorithms / listings, idx)

            std::string s_aux_list;
            if(!ReadFile(s_aux_list, s_aux_list_name.c_str()))
                throw std::runtime_error("getting a list of aux files failed");
            // read it back

            Split(aux_list, s_aux_list, "\n", 0); // split the list by newlines
            std::for_each(aux_list.begin(), aux_list.end(), TrimSpace); // remove leading or trailing spaces
            std::sort(aux_list.begin(), aux_list.end()); // sort the filenames
            aux_list.erase(std::unique(aux_list.begin(), aux_list.end()), aux_list.end()); // remove duplicates (empty entries, whitespace, ...)
            std::vector<std::string>::iterator p_empty_it;
            if((p_empty_it = std::find(aux_list.begin(), aux_list.end(), std::string())) != aux_list.end())
                aux_list.erase(p_empty_it); // erase any empty entries
            // split to the individual entries

            remove(s_aux_list_name.c_str());
            // remove the temp file
        }
        // get a list of all the aux files

        std::string s_hash_string;
        {
            SHA1Context sha1;
            SHA1Reset(&sha1);
            for(size_t i = 0, n = aux_list.size(); i < n; ++ i) {
                SHA1Input(&sha1, aux_list[i].c_str(), aux_list[i].size() * sizeof(char));
                // hash the file name

                std::string s_aux_file;
                if(!ReadFile(s_aux_file, aux_list[i].c_str()))
                    throw std::runtime_error("failed to read one or more aux file(s)");
                SHA1Input(&sha1, s_aux_file.c_str(), s_aux_file.size() * sizeof(char));
                // hash the file contents
            }
            uint8_t Message_Digest[SHA1HashSize];
            SHA1Result(&sha1, Message_Digest);
            for(int i = 0; i < SHA1HashSize; ++ i) {
                std::string s_hash_digit;
                Format(s_hash_digit, "%02x", Message_Digest[i]);
                s_hash_string += s_hash_digit;
            }
        }
        // get SHA1 of the concatenated files

        std::string s_hashes_name = std::string(p_s_jobname) + "_aux.sha1";

        std::string s_hashes;
        if(!b_rerun && !ReadFile(s_hashes, s_hashes_name.c_str())) {
            printf("IsTexReady: should rerun latex as per missing aux file hashes\n");
            b_rerun = true;
        }
        // read aux file hashes. if not found, should run again

        TrimSpace(s_hashes);
        if(!b_rerun && s_hashes != s_hash_string) {
            printf("IsTexReady: should rerun latex as the aux files have changed\n");
            b_rerun = true;
        }

        if(b_rerun) {
            FILE *p_fw;
#if defined(_MSC_VER) && !defined(__MWERKS__) && _MSC_VER >= 1400
            if(fopen_s(&p_fw, s_hashes_name.c_str(), "w"))
#else //_MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
            if(!(p_fw = fopen(s_hashes_name.c_str(), "w")))
#endif //_MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
                throw std::runtime_error("failed to write the digest of the aux files");
            fprintf(p_fw, "%s\n", s_hash_string.c_str());
            fclose(p_fw);
            // save a new hashes file

            return 1;
            // signal to rerun
        }
        // should we rerun?

        printf("IsTexReady: no need to run latex again\n");
    } catch(std::bad_alloc&) {
        fprintf(stderr, "error: uncaught std::bad_alloc\n");
        return -1;
    } catch(std::runtime_error &r_exc) {
        fprintf(stderr, "error: uncaught std::runtime_error: \'%s\'\n", r_exc.what());
        return -1;
    } catch(std::exception&) {
        fprintf(stderr, "error: uncaught std::exception\n");
        return -1;
    }

    return 0; // noneed to run again, all is ready.
}

// ------------------------------------------------------------------------------------------------

// SHA-1 due to https://tools.ietf.org/html/rfc3174

#ifndef _SHA_enum_
#define _SHA_enum_
enum
{
    shaSuccess = 0,
    shaNull,            /* Null pointer parameter */
    shaInputTooLong,    /* input data too long */
    shaStateError       /* called Input after Result */
};
#endif

TODO - paste sha1.c from https://tools.ietf.org/html/rfc3174 in here 

只需将其构建为控制台应用程序IsTexReady.exe,然后可以将脚本修改为:

@echo off

IsTexReady.exe main > nul
rem run once just to hash the aux files, ignore the results

set N=0

:again

bibtex main
bibtex own
pdflatex -synctex=1 main
rem run tex (use -synctex=1 to get clickable cross-referencing between tex and pdf)

set /A N=%N%+1

IsTexReady.exe main
rem run again, see if log prompts to re-run or whether the aux files changed since the beginning

if %ERRORLEVEL% == 1 (

if %N% == 5 (
echo "maximum number of iterations reached"
exit
)
rem see if the maximum number of iterations would be exceeded

goto again
)
rem see if we need to loop

echo "done in %N% iterations"

然后它会根据需要多次重新运行 latex。它会保存辅助文件的 SHA1 哈希值来检测更改,而不是像 texify 那样复制辅助文件。请注意,texify 对最大循环次数也有限制(默认值5)。

源代码也应该可以在 Unix 上运行(构建g++ --ansi并可能修复一些问题,因为我还没有测试过)。但话又说回来,你可以在纯 bash 中使用 、 和 执行grep类似lsfor脚本md5sum

答案2

正确且最终可移植的方法是使用重新运行文件检查包。然后你需要做的就是:

bibtex main
bibtex own
#makeindex main # or not
pdflatex -synctex=1 main

然后检查或main.log的出现情况。如果找到,则再次循环脚本。Rerun to getRerun LaTeX

相关内容