To answer your question: No, you cannot avoid the new line character that gets inserted. See below for how I arrived at this conclusion.
I find the source code to TeX very confusing so it's certainly possible that I'm missing something here. The way I read this, \write is implemented as an extension, not because it's actually an extension, just that Knuth wanted to give an example of writing one. In particular, \write is implemented as a whatsit node:
@<Implement \.{\\write}@>=
begin k:=cur_cs; new_write_whatsit(write_node_size);@/
cur_cs:=k; p:=scan_toks(false,false); write_tokens(tail):=def_ref;
end
Rather than track down exactly how whatsits are treated, it suffices to look for the implementation of \immediate since \immediate\write performs the write immediately. The relevant portion of the code is the following.
@<Implement \.{\\immediate}@>=
begin get_x_token;
if cur_cmd=extension then begin
if cur_chr<=close_node then
begin p:=tail; do_extension; {append a whatsit node}
out_what(tail); {do the action immediately}
flush_node_list(tail); tail:=p; link(p):=null;
end
Clearly, this is appending a whatsit node, executing the node, flushing it (presumably freeing the memory associated with it), and then restoring the linked list to what it was before it appended the node. What we're interested in here is out_what. Again, the relevant portion follows.
procedure out_what(@!p:pointer);
var j:small_number; {write stream number}
begin case subtype(p) of
open_node,write_node,close_node:@<Do some work that has been queued up
for \.{\\write}@>;
@<Do some work that has been queued up...@>=
if not doing_leaders then
begin j:=write_stream(p);
if subtype(p)=write_node then write_out(p)
Obviously, we want write_out.
procedure write_out(@!p:pointer);
var old_setting:0..max_selector; {holds print |selector|}
@!old_mode:integer; {saved |mode|}
@!j:small_number; {write stream number}
@!q,@!r:pointer; {temporary variables for list manipulation}
begin @<Expand macros in the token list
and make |link(def_ref)| point to the result@>;
old_setting:=selector; j:=write_stream(p);
if write_open[j] then selector:=j
else begin {write to the terminal if file isn't open}
if (j=17)and(selector=term_and_log) then selector:=log_only;
print_nl("");
end;
token_show(def_ref); print_ln;
flush_list(def_ref); selector:=old_setting;
end;
The global variable selector is used to determine where output should be directed. In the case of a \write to a file, it will be a number 0–15. The line if write_open[j] then selector:=j sets the selector to j. token_show(def_ref) prints the tokens pointed to by def_ref based on selector. Then print_ln is called.
@ To end a line of text output, we call |print_ln|.
@<Basic print...@>=
procedure print_ln; {prints an end-of-line}
begin case selector of
term_and_log: begin wterm_cr; wlog_cr;
term_offset:=0; file_offset:=0;
end;
log_only: begin wlog_cr; file_offset:=0;
end;
term_only: begin wterm_cr; term_offset:=0;
end;
no_print,pseudo,new_string: do_nothing;
othercases write_ln(write_file[selector])
endcases;@/
end; {|tally| is not affected}
As that comment says, print_ln is used to end a line of text.
It's actually at this point that I get stuck. I can't find a definition for write_ln. My best guess is that WEB removes underscores so that write_ln becomes the pascal writeln which the web2c binary fixwrites converts. From the top of fixwrites.c:
/* fixwrites -- convert Pascal write/writeln's into fprintf's or putc's.
Originally by Tim Morgan, October 10, 1987. */
At any rate, the very first function in the generated pdftex0.c is
void
println ( void )
{
println_regmem
switch ( selector )
{
/* elided cases 17 through 21 */
default:
putc ('\n', writefile [selector ]);
break;
}
}