do_bang(
    int		addr_count,
    exarg_T	*eap,
    int		forceit,
    int		do_in,
    int		do_out)
{
    char_u		*arg = eap->arg;	/* command */
    linenr_T		line1 = eap->line1;	/* start of range */
    linenr_T		line2 = eap->line2;	/* end of range */
    char_u		*newcmd = NULL;		/* the new command */
    int			free_newcmd = FALSE;    /* need to free() newcmd */
    int			ins_prevcmd;
    char_u		*t;
    char_u		*p;
    char_u		*trailarg;
    int			len;
    int			scroll_save = msg_scroll;

    /*
     * Disallow shell commands for "rvim".
     * Disallow shell commands from .exrc and .vimrc in current directory for
     * security reasons.
     */
    if (check_restricted() || check_secure())
	return;

    if (addr_count == 0)		/* :! */
    {
	msg_scroll = FALSE;	    /* don't scroll here */
	autowrite_all();
	msg_scroll = scroll_save;
    }

    /*
     * Try to find an embedded bang, like in :!<cmd> ! [args]
     * (:!! is indicated by the 'forceit' variable)
     */
    ins_prevcmd = forceit;
    trailarg = arg;
    do
    {
	len = (int)STRLEN(trailarg) + 1;
	if (newcmd != NULL)
	    len += (int)STRLEN(newcmd);
	if (ins_prevcmd)
	{
	    if (prevcmd == NULL)
	    {
		emsg(_(e_noprev));
		vim_free(newcmd);
		return;
	    }
	    len += (int)STRLEN(prevcmd);
	}
	if ((t = alloc((unsigned)len)) == NULL)
	{
	    vim_free(newcmd);
	    return;
	}
	*t = NUL;
	if (newcmd != NULL)
	    STRCAT(t, newcmd);
	if (ins_prevcmd)
	    STRCAT(t, prevcmd);
	p = t + STRLEN(t);
	STRCAT(t, trailarg);
	vim_free(newcmd);
	newcmd = t;

	/*
	 * Scan the rest of the argument for '!', which is replaced by the
	 * previous command.  "\!" is replaced by "!" (this is vi compatible).
	 */
	trailarg = NULL;
	while (*p)
	{
	    if (*p == '!')
	    {
		if (p > newcmd && p[-1] == '\\')
		    STRMOVE(p - 1, p);
		else
		{
		    trailarg = p;
		    *trailarg++ = NUL;
		    ins_prevcmd = TRUE;
		    break;
		}
	    }
	    ++p;
	}
    } while (trailarg != NULL);

    vim_free(prevcmd);
    prevcmd = newcmd;

    if (bangredo)	    /* put cmd in redo buffer for ! command */
    {
	/* If % or # appears in the command, it must have been escaped.
	 * Reescape them, so that redoing them does not substitute them by the
	 * buffername. */
	char_u *cmd = vim_strsave_escaped(prevcmd, (char_u *)"%#");

	if (cmd != NULL)
	{
	    AppendToRedobuffLit(cmd, -1);
	    vim_free(cmd);
	}
	else
	    AppendToRedobuffLit(prevcmd, -1);
	AppendToRedobuff((char_u *)"\n");
	bangredo = FALSE;
    }
    /*
     * Add quotes around the command, for shells that need them.
     */
    if (*p_shq != NUL)
    {
	newcmd = alloc((unsigned)(STRLEN(prevcmd) + 2 * STRLEN(p_shq) + 1));
	if (newcmd == NULL)
	    return;
	STRCPY(newcmd, p_shq);
	STRCAT(newcmd, prevcmd);
	STRCAT(newcmd, p_shq);
	free_newcmd = TRUE;
    }
    if (addr_count == 0)		/* :! */
    {
	/* echo the command */
	msg_start();
	msg_putchar(':');
	msg_putchar('!');
	msg_outtrans(newcmd);
	msg_clr_eos();
	windgoto(msg_row, msg_col);

	do_shell(newcmd, 0);
    }
    else				/* :range! */
    {
	/* Careful: This may recursively call do_bang() again! (because of
	 * autocommands) */
	do_filter(line1, line2, eap, newcmd, do_in, do_out);
	apply_autocmds(EVENT_SHELLFILTERPOST, NULL, NULL, FALSE, curbuf);
    }
    if (free_newcmd)
	vim_free(newcmd);
}