The following function works for arbitrary arrays, with any nesting structure and for any shape of the arrays, as long as they are all 2D arrays. Multidimensional arrays are not supported (same as mat2str).
The function also allows specifying arbitrary row and column separators for cell arrays (for example, to choose between comma and space), and optionally forcing those separators for non-cell arrays too (thus overriding mat2str'behaviour). Default separators in cell arrays are ' ' for columns and '; ' for rows.
function y = array2str(x, col_sep, row_sep, sep_noncell)
% Converts a (possibly cell, nested) array to string representation
%
% Optional inputs col_sep and row_sep specify separators for the cell arrays.
% They can be arbitrary strings (but they should be chosen as per Matlab rules
% so that the output string evaluates to the input). Optional flag sep_noncell
% can be used to force those separators with non-cell arrays too, instead of
% the separators produced by mat2str (space and semicolon)
% Default values
if nargin<4
    sep_noncell = false;
end
if nargin<3
    row_sep = '; ';
end
if nargin<2
    col_sep = ' ';
end
x = {x}; % this is to initiallize processing
y = {[]}; % [] indicates content unknown yet: we need to go on
done = false;
while ~done
    done = true; % tentatively
    for n = 1:numel(y);
        if isempty(y{n}) % we need to go deeper
            done = false;
            if ~iscell(x{1}) % we've reached ground
                s = mat2str(x{1}); % final content
                if sep_noncell % replace mat2str's separators if required
                    s = regexprep(s,'(?<=^[^'']*(''[^'']*'')*[^'']*) ', col_sep);
                    s = regexprep(s,'(?<=^[^'']*(''[^'']*'')*[^'']*);', row_sep);
                end
                y{n} = s; % put final content...
                x(1) = []; % ...and remove from x
            else % advance one level
                str = ['{' repmat([{[]}, col_sep], 1, numel(x{1})) '}'];
                ind_sep = find(cellfun(@(t) isequal(t, col_sep), str));
                if ~isempty(ind_sep)
                    str(ind_sep(end)) = []; % remove last column separator
                    ind_sep(end) = [];
                end
                step_sep = size(x{1}, 2);
                str(ind_sep(step_sep:step_sep:end)) = {row_sep};
                y = [y(1:n-1) str y(n+1:end)]; % mark for further processing...
                x = [reshape(x{1}.', 1, []) x(2:end)]; % ...and unbox x{1},
                    % transposed and linearized
            end
        end
    end
end
y = [y{:}]; % concatenate all strings
The above function uses regular expressions to force the specified separators in non-cell arrays. This works in Matlab but not in Octave, due to limitations in supported lookbehind patterns. The following modified version avoids regular expressions, and thus works in Matlab and in Octave. Only the part between if sep_noncell and the matching end changes with respect to the first version.
function y = array2str(x, col_sep, row_sep, sep_noncell)
% Converts a (possibly cell, nested) array to string representation.
% Octave-friendly version
%
% Optional inputs col_sep and row_sep specify separators for the cell arrays.
% They can be arbitrary strings (but they should be chosen as per Matlab rules
% so that the output string evaluates to the input). Optional flag sep_noncell
% can be used to force those separators with non-cell arrays too, instead of
% the separators produced by mat2str (space and semicolon)
% Default values
if nargin<4
    sep_noncell = false;
end
if nargin<3
    row_sep = '; ';
end
if nargin<2
    col_sep = ' ';
end
x = {x}; % this is to initiallize processing
y = {[]}; % [] indicates content unknown yet: we need to go on
done = false;
while ~done
    done = true; % tentatively
    for n = 1:numel(y);
        if isempty(y{n}) % we need to go deeper
            done = false;
            if ~iscell(x{1}) % we've reached ground
                s = mat2str(x{1}); % final content
                if sep_noncell % replace mat2str's separators if required
                    for k = flip(find(~mod(cumsum(s==''''),2) & s==' ')) % process
                        % backwards, because indices to the right will become invalid
                        s = [s(1:k-1) col_sep s(k+1:end)];
                    end
                    for k = flip(find(~mod(cumsum(s==''''),2) & s==';'))
                        s = [s(1:k-1) row_sep s(k+1:end)];
                    end
                end
                y{n} = s; % put final content...
                x(1) = []; % ...and remove from x
            else % advance one level
                str = ['{' repmat([{[]}, col_sep], 1, numel(x{1})) '}'];
                ind_sep = find(cellfun(@(t) isequal(t, col_sep), str));
                if ~isempty(ind_sep)
                    str(ind_sep(end)) = []; % remove last column separator
                    ind_sep(end) = [];
                end
                step_sep = size(x{1}, 2);
                str(ind_sep(step_sep:step_sep:end)) = {row_sep};
                y = [y(1:n-1) str y(n+1:end)]; % mark for further processing...
                x = [reshape(x{1}.', 1, []) x(2:end)]; % ...and unbox x{1},
                    % transposed and linearized
            end
        end
    end
end
y = [y{:}]; % concatenate all strings
How it works
I chose a non-recursive approach because I'm usually more confortable with iteration than with recursion.
The output is gradually built by keeping substrings or empty arrays ([]) in a cell array (y). An empty array in a cell of y indicates "further processing is needed". Substrings define the "structure", or eventually the numeric, character or logical contents in the deepest level of the cell nesting.
In each iteration, the first empty array found in y is substituted by actual content, or by substrings and other empty arrays to be processed later. When y doesn't contain any empty array the process ends, and all substrings of y are concatenated to obtain the final string output.
For example, given input x = {[10 20], {'abc'; false; true;}}; and calling y = array2str(x) the array y in each step is a cell array containing:
'{'   []   ', '   []   '}'
'{'   '[10 20]'   ', '   []   '}'
'{'   '[10 20]'   ', '   '{'   []   '; '   []   '; '   []   '}'   '}'
'{'   '[10 20]'   ', '   '{'   ''abc''   '; '   []   '; '   []   '}'   '}'
'{'   '[10 20]'   ', '   '{'   ''abc''   '; '   'false'   '; '   []   '}'   '}'
'{'   '[10 20]'   ', '   '{'   ''abc''   '; '   'false'   '; '   'true'   '}'   '}'
and the latter is finally concatenated into the string
'{[10 20] {''abc''; false; true}}'
As an example with custom separators, array2str(x, ', ', '; ', true) would give
'{[10, 20], {''abc''; false; true}}'