我正在使用 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
类似ls
的for
脚本md5sum
。
答案2
正确且最终可移植的方法是使用重新运行文件检查包。然后你需要做的就是:
bibtex main
bibtex own
#makeindex main # or not
pdflatex -synctex=1 main
然后检查或main.log
的出现情况。如果找到,则再次循环脚本。Rerun to get
Rerun LaTeX