How to write macro with variable amount of text variables

You're overthinking: the tool is already there, namely tabular.

\documentclass{article}

\newcommand{\sigblock}[3]{%
  \par\vspace{\medskipamount}\noindent
  \hspace*{#1in}\makebox[#2in]{\hrulefill}\\*[.2ex]
  \hspace*{#1in}%
  \begin{tabular}{@{}l@{}}
  #3
  \end{tabular}%
}

\begin{document}

\sigblock{0}{3}{Notary Public \\ At Large}

\sigblock{0}{3}{Notary Public}

\sigblock{1}{3}{Notary Public \\ At Large \\ Again \\ What else?}

\end{document}

enter image description here

If you need to add \singlespacing, remember it is not a command with an argument, but a declaration. So you should have

\newcommand{\sigblock}[3]{%
  \par{\singlespacing\vspace{\medskipamount}\noindent
  \hspace*{#1in}\makebox[#2in]{\hrulefill}\\*[.2ex]
  \hspace*{#1in}%
  \begin{tabular}{@{}l@{}}
  #3
  \end{tabular}%
  \par}%
}

By trying to solve this problem I've come up with a possibly interesting macro, so I want to share it here. This macro is called \vardef and allows us to define a macro which can receive a variable number of arguments (any number, not limited to 9 arguments) enclosed in curly brackets.

Inside the definition of a \vardefined macro one can use the macro \NUMARGS for retrieving the number of arguments the macro receives when it is "executed"; and one can use the macros \ARG1, \ARG2, \ARG3,... for retrieving the arguments of the macro (if they exist).

If, for example, one \vardefines the macro \mymacro as follows,

\newcount\tmpnum
\vardef\mymacro{This macro is being used with \NUMARGS{} arguments.
  They are: \ifnum\NUMARGS>0 \tmpnum=1 \loop \ARG{\the\tmpnum}
  \ifnum\NUMARGS>\tmpnum \advance\tmpnum by 1 \repeat\fi}

then the "execution" of \mymacro{first}{second}{third} gives the "output"

 This macro is being used with 3 arguments. They are: first second third

and the "execution" of \mymacro{first}{second}{third}{fourth}{fifth} gives

This macro is being used with 5 arguments. They are: first second third fourth fifth

This is how \vardef is defined:

% Firstly we create a new counter to store the number 
% of the current argument that is being read. 
% Arguments start from 1, so we initially set it to 1.
\newcount\vardefnum
\vardefnum=1

% \vardef receives two arguments: the first is the name of
% the macro to be \vardef-ined and the second is its definition.
\def\vardef#1#2{%
  % We momentaneously store the definition in a macro called
  % \vardefinition for later use.
  \def\vardefinition{#2}
  % Now we actually define our control sequence.  We define it
  % basically to be \grab (a macro whose purpose is explained below).
  \def#1{%
    % Before actually grabbing arguments with \grab, we give
    % an empty useless definition to \invardef so  that we will be able
    % later to know that we are in the defining code  of a
    % \vardef-ined macro (we will see why later).
    \def\invardef{}%
    \grab}}

% The purpose of \grab is to collect and store all the arguments
% that follow him and to finally actually "execute" the code we
% previously stored in \vardefinition.
% To do that, \grab starts by saving into \next the first token
% after him. Then he passes the job to his sister \grabA.
\def\grab{\futurelet\next\grabA}

% \grabA checks if the token stored in \next is an opening curly
% brackets (or, more properly, a \bgroup). If so, she invokes
% the macro \storeargs. Otherwise she defines the number of
% arguments \numvarargs collected by now to be one less than
% \vardefnum;  then she expands \vardefinition and removes
% the traces of her job by calling \clearvardef.
\def\grabA{\ifx\next\bgroup\storearg\else\advance\vardefnum by-1
  \edef\numvarargs{\the\vardefnum}\vardefinition\clearvardef\fi}

% \clearvardef undefines \invardef (because now we are exiting
% the argument of the \vardef-ined macro) and restores the
% \vardefnum counter.
\def\clearvardef{\let\invardef\undefined \vardefnum=1\relax}

% \storearg, when is invoked by \grabA, finds on its way the
% sequence \else\advance\vardefnum ... \clearvardef\fi. He wants
% to get rid of it to be able to see what's behind it:  so he
% eats all of it using his first argument (the argument #1).
% Now \storearg sees after him an argument enclosed in curly
% brackets. So he puts it in his second argument (the argument #2).
% Next, to make TeX happy, \storearg has to regurgitate
% the \fi he has previously eaten up. After that he defines a new
% control sequence that stores his second argument he has just
% collected. The name of this control sequence starts with
% 'vararg' and ends with the number present in \vardefnum at that
% moment. Finally \storearg increments \vardefnum and takes a rest
% by giving the job back to \grab.
\def\storearg#1\fi#2{\fi
  \expandafter\def\csname vararg\the\vardefnum\endcsname{#2}%
  \advance\vardefnum by 1\grab}


% The following two macros, \ARG and \NUMARGS, are the user
% interface for actually using inside the \vardef-inition the
% (variable number of) arguments.  Ckecking if \invardef is
% defined, they can know if they are called inside the
% \vardef-inition of a macro, and they can throw an error message
% otherwise. What they do is quite self explanatory.
\def\ARG#1{\ifdefined\invardef\else\errmessage{%
  \string\ARG\space has meaning only inside a
  \string\vardef-ined macro.}\fi
  \ifnum#1<1\errmessage{The argument of \string\ARG\space
  must be greater than zero!}\fi
  \ifnum#1>\numvarargs\errmessage{\string\ARG#1 does not exist!}\fi
  \csname vararg#1\endcsname}

\def\NUMARGS{\ifdefined\invardef\numvarargs\else\errmessage{%
  \string\NUMARGS\space has meaning only inside a
  \string\vardef-ined macro.}\fi}

The solution to the asked question then is really simple: use \ARG1, \ARG2, \ARG3,... instead of #1, #2, #3,... and use \NUMARGS to check how many arguments have been passed to \sigblock.

\documentclass{article}
\usepackage{setspace}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% here goes the above definition of \vardef %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\vardef\sigblock{\singlespacing
  \vbox{\vskip.75in\noindent\hskip\ARG1in
  {\hbox to \ARG2in{\leaders\hbox to 0.00625in{\hfil.\hfil}\hfill}}\endgraf
  \noindent\hskip\ARG1in\ARG3
  \ifnum\NUMARGS=4\endgraf\noindent\hskip\ARG1 in\ARG4\fi}}

\begin{document}

\sigblock{0}{3}{Notary Public}{At Large}

\sigblock{0}{3}{Notary Public}

\end{document}

enter image description here

And now it is extremely easy to use any number of arguments in \sigblock.

NOTE: Differently from normal macros, in a \vardefined macro all arguments have to be enclosed in curly brackets and there can't be spaces between the closing brace of an argument and the opening brace of the following argument.


Though I recommend to use the answer from @egreg here is a way to implement what you are looking for using \@ifnextchar. The idea is that \sigblock prepares everything until it comes to processing the lines beneath the signature field. Then \sigblock@ is invoked which will set the next grouped argument as a line beneath the signature field and starts a recursion. The recursion terminates when there are no arguments left over:

\def\sigblock@#1#2{%
  \par\hskip#1in#2
  \@ifnextchar\bgroup{\sigblock@{#1}}\endgroup
}

output1_cut output2_cut output3_cut

\documentclass{article}
\usepackage{lipsum}
\usepackage[doublespacing]{setspace}%doublespacing is active for testing purposes

\makeatletter
\def\sigblock#1#2#3{%
  \begingroup
  \singlespacing
  \parindent\z@
  \vskip.75in
  \vbox{%
    \hskip#1in%
    \hbox to #2in{%
      \leaders\hbox to 0.00625in{\hfil.\hfil}\hfill
    }
  }%
  \sigblock@{#1}{#3}
}
\def\sigblock@#1#2{%
  \par\hskip#1in#2\par
  \@ifnextchar\bgroup{\sigblock@{#1}}\endgroup
}
\makeatother


\begin{document}
\sigblock{0}{3}{Notary Public}{At Large}{At Large}{At Large}{At Large}{At Large}
\lipsum
\end{document}

Note. As the extra lines are optional you might want to write \sigblock{0}{3}{Notary Public}[At Large]... sticking to the standard interface design with brackets denoting optional arguments. In this case you would define:

\def\sigblock#1#2#3{%
  \begingroup
  \singlespacing
  \parindent\z@
  \vskip.75in
  \vbox{%
    \hskip#1in%
    \hbox to #2in{%
      \leaders\hbox to 0.00625in{\hfil.\hfil}\hfill
    }
  }%
  \sigblock@{#1}[#3]
}
\def\sigblock@#1[#2]{%
  \par\hskip#1in#2\par
  \@ifnextchar[{\sigblock@{#1}}\endgroup
}