Home » What is the difference between ‘env’ and ‘printenv’?

What is the difference between ‘env’ and ‘printenv’?


Are there any historical reasons for there being two commands instead
of one?

There was, just history manner.

  1. Bill Joy wrote the first version of printenv command in 1979 for BSD.
  2. UNIX System III introduced env command in 1980.
  3. GNU followed UNIX System’s env in 1986.
  4. BSD followed GNU/UNIX System’s env in 1988.
  5. MINIX followed BSD’s printenv in 1988.
  6. GNU followed MINX/BSD’s printenv in 1989.
  7. GNU shell programming utilities 1.0 included printenv and env in 1991.
  8. GNU Shell Utilities merged into GNU coreutils in 2002, which is what you find in GNU/Linux nowadays.

Note that the “followed” doesn’t means the source code was the same, probably they were rewritten to avoid license lawsuits.

So, the reason why both commands existed is because Bill Joy wrote printenv even before env existed.
After 10 years of merging/compatibility and GNU come across it, you are now seeing both similar commands on the same page.

This history indicated as follows: (I tried to minimize the answer and only provided 2 essential source code snippets here. The rest you can click the attached links to see more)

[fall of 1975]

Also arriving in the fall of 1975 were two unnoticed graduate students, Bill Joy and Chuck Haley; they both took an immediate interest in the new system. Initially, they began working on a Pascal system that Thompson had hacked together while hanging around the 11/70 machine room.


Joy started compiling the first Berkeley Software Distribution (1BSD), which was released on March 9, 1978. //rf: https://en.wikipedia.org/wiki/Berkeley_Software_Distribution

[February, 1979]

1979(see “Bill Joy, UCB February, 1979”) /1980(see “copyright[] =”)
, printenv.c //rf: http://minnie.tuhs.org/cgi-bin/utree.pl?file=2.11BSD/src/ucb/printenv.c

 * Copyright (c) 1980 Regents of the University of California.
 * All rights reserved.  The Berkeley software License Agreement
 * specifies the terms and conditions for redistribution.

#ifndef lint
char copyright[] =
"@(#) Copyright (c) 1980 Regents of the University of California.n
 All rights reserved.n";
#endif not lint

#ifndef lint
static char sccsid[] = "@(#)printenv.c  5.1 (Berkeley) 5/31/85";
#endif not lint

 * printenv
 * Bill Joy, UCB
 * February, 1979

extern  char **environ;

main(argc, argv)
    int argc;
    char *argv[];
    register char **ep;
    int found = 0;

    argc--, argv++;
    if (environ)
        for (ep = environ; *ep; ep++)
            if (argc == 0 || prefix(argv[0], *ep)) {
                register char *cp = *ep;

                if (argc) {
                    while (*cp && *cp != '=')
                    if (*cp == '=')
                printf("%sn", cp);
    exit (!found);

prefix(cp, dp)
    char *cp, *dp;

    while (*cp && *dp && *cp == *dp)
        cp++, dp++;
    if (*cp == 0)
        return (*dp == '=');
    return (0);


Hard to determine released in 2BSD OR 3BSD //rf: https://en.wikipedia.org/wiki/Berkeley_Software_Distribution

  • 3BSD
    The printenv command appeared in 3.0 BSD. //rf: http://www.freebsd.org/cgi/man.cgi?query=printenv&sektion=1#end
    3.0 BSD introduced at 1979 //rf: http://gunkies.org/wiki/3_BSD

  • 2BSD
    The printenv command first appeared in 2BSD //rf: http://man.openbsd.org/printenv.1

[June, 1980]

UNIX Release 3.0 OR “UNIX System III” //rf: ftp://pdp11.org.ru/pub/unix-archive/PDP-11/Distributions/usdl/SysIII/

[xiaobai@xiaobai pdp11v3]$ sudo grep -rni printenv . //no such printenv exist.
[xiaobai@xiaobai pdp11v3]$ sudo find . -iname '*env*'
[xiaobai@xiaobai pdp11v3]$ man ./sys3/usr/src/man/man1/env.1 | cat //but got env already
ENV(1)                                                                General Commands Manual                                                                ENV(1)

       env - set environment for command execution

       env [-] [ name=value ] ...  [ command args ]

       Env obtains the current environment, modifies it according to its arguments, then executes the command with the modified environment.  Arguments of the form
       name=value are merged into the inherited environment before the command is executed.  The - flag causes the inherited environment to be ignored  completely,
       so that the command is executed with exactly the environment specified by the arguments.

       If no command is specified, the resulting environment is printed, one name-value pair per line.

       sh(1), exec(2), profile(5), environ(7).

[xiaobai@xiaobai pdp11v3]$ 
[xiaobai@xiaobai pdp11v3]$ cat ./sys3/usr/src/cmd/env.c //diff with http://minnie.tuhs.org/cgi-bin/utree.pl?file=pdp11v/usr/src/cmd/env.c version 1.4, you will know this file is slightly older, so we can concluded that this file is "env.c version < 1.4"
 *      env [ - ] [ name=value ]... [command arg...]
 *      set environment, then execute command (or print environment)
 *      - says start fresh, otherwise merge with inherited environment
#include <stdio.h>

#define NENV    100
char    *newenv[NENV];
char    *nullp = NULL;

extern  char **environ;
extern  errno;
extern  char *sys_errlist[];
char    *nvmatch(), *strchr();

main(argc, argv, envp)
register char **argv, **envp;

        if (argc && strcmp(*argv, "-") == 0) {
                envp = &nullp;

        for (; *envp != NULL; envp++)
                if (strchr(*envp, '=') != NULL)
        while (*argv != NULL && strchr(*argv, '=') != NULL)

        if (*argv == NULL)
        else {
                environ = newenv;
                execvp(*argv, argv);
                fprintf(stderr, "%s: %sn", sys_errlist[errno], *argv);

register char *arg;
        register char **p;

        for (p = newenv; *p != NULL && p < &newenv[NENV-1]; p++)
                if (nvmatch(arg, *p) != NULL) {
                        *p = arg;
        if (p >= &newenv[NENV-1]) {
                fprintf(stderr, "too many values in environmentn");
        *p = arg;

        register char **p = newenv;

        while (*p != NULL)
                printf("%sn", *p++);

 *      s1 is either name, or name=value
 *      s2 is name=value
 *      if names match, return value of s2, else NULL

static char *
nvmatch(s1, s2)
register char *s1, *s2;

        while (*s1 == *s2++)
                if (*s1++ == '=')
        if (*s1 == '' && *(s2-1) == '=')
[xiaobai@xiaobai pdp11v3]$


BSD first printenv manual //rf: http://minnie.tuhs.org/cgi-bin/utree.pl?file=2.11BSD/src/man/man1/printenv.1

I couldn’t find the manual related to env, but the closest is getenv and environ //http://minnie.tuhs.org/cgi-bin/utree.pl?file=2.11BSD/src/man


First version of GNU env //rf: ftp://ftp-archive.freebsd.org/pub/FreeBSD-Archive/old-releases/i386/1.0-RELEASE/ports/shellutils/src/env.c


MINIX 1st released //rf: https://en.wikipedia.org/wiki/Andrew_S._Tanenbaum

  • Tanenbaum wrote a clone of UNIX, called MINIX (MINi-unIX), for the IBM PC. It was targeted at students and others who wanted to learn how an operating system worked.


BSD 1st env.c //http://minnie.tuhs.org/cgi-bin/utree.pl?file=2.11BSD/src/usr.sbin/cron/env.c

/* Copyright 1988,1990,1993,1994 by Paul Vixie
 * All rights reserved

[October 4, 1988]

MINIX version 1.3 //rf: https://groups.google.com/forum/#!topic/comp.os.minix/cQ8kaiq1hgI

32932 190 /minix/commands/printenv.c //printenv.c already exist

//rf: http://www.informatica.co.cr/linux/research/1990/0202.htm


The first version of GNU printenv, refer to [August 12, 1993].

[July 16, 1991]

“Shellutils” – GNU shell programming utilities 1.0 released
//rf: https://groups.google.com/forum/#!topic/gnu.announce/xpTRtuFpNQc

The programs in this package are:

basename date dirname env expr groups id logname pathchk printenv
printf sleep tee tty whoami yes nice nohup stty uname

[August 12, 1993]

printenv.c //rf: ftp://ftp-archive.freebsd.org/pub/FreeBSD-Archive/old-releases/i386/1.0-RELEASE/ports/shellutils/src/printenv.c

, GNU Shell Utilities 1.8 //rf: ftp://ftp-archive.freebsd.org/pub/FreeBSD-Archive/old-releases/i386/1.0-RELEASE/ports/shellutils/VERSION

/* printenv -- print all or part of environment
   Copyright (C) 1989, 1991 Free Software Foundation.


printenv.c which found on DSLinux source code in 2006 //rf: (Google) cache:mailman.dslinux.in-berlin.de/pipermail/dslinux-commit-dslinux.in-berlin.de/2006-August/000578.html

--- NEW FILE: printenv.c ---
 * Copyright (c) 1993 by David I. Bell

[November 1993]

The first version of FreeBSD was released. //rf: https://en.wikipedia.org/wiki/FreeBSD

[september 1, 2002]


The GNU fileutils, textutils, and sh-utils(see “Shellutils” at July 16, 1991 above) packages have been merged into one, called the GNU coreutils.

Overall, env use cases compare with printenv:

  1. print environment variables, but printenv can do the same

  2. Disable shell builtin but can achieve with enable cmd too.

  3. set variable but pointless due to some shells already can do it without env, e.g.

    $ HOME=/dev HOME=/tmp USER=root /bin/bash -c “cd ~; pwd”


  4. #!/usr/bin/env python header, but still not portable if env not in /usr/bin

  5. env -i, disable all env. I find it useful to figure out the critical environment variables for certain program, to make it run from crontab. e.g. [1] In interactive mode, run declare -p > /tmp/d.sh to stores attributes variables. [2] In /tmp/test.sh, write: . /tmp/d.sh; eog /home/xiaobai/Pictures/1.jpg [3] Now run env -i bash /tmp/test.sh [4] If it success to display image, remove half of variables in /tmp/d.sh and run env -i bash /tmp/test.sh again. If something failed, undo it. Repeat the step to narrow down. [5] Finally I figure out eog requires $DISPLAY to run in crontab, and absent of $DBUS_SESSION_BUS_ADDRESS will slow down the display of image.

  6. target_PATH="$PATH:$(sudo printenv PATH)"; is useful to directly use the root path without having to further parse the output of env or printenv.


xb@dnxb:~$ sudo env | grep PATH
xb@dnxb:~$ sudo printenv | grep PATH
xb@dnxb:~$ sudo printenv PATH
xb@dnxb:~$ sudo env PATH
env: ‘PATH’: No such file or directory

Having a different point of view (from FreeBSD), you have:

From man env:

 The env utility executes another utility after modifying the environment
 as specified on the command line.  Each name=value option specifies the
 setting of an environment variable, name, with a value of value.  All
 such environment variables are set before the utility is executed.
 If no utility is specified, env prints out the names and values of the
 variables in the environment, with one name/value pair per line.

From man printenv:

 The printenv utility prints out the names and values of the variables in
 the environment, with one name/value pair per line.  If name is speci-
 fied, only its value is printed.

So these commands might have the same effect without argument, but printenv sole purpose is to display the current environment key/values while env goal it to set some environment before calling another binary/script/whatever.

Is it more clear this way ?

To known more:

  • man 1 env (FreeBSD)
  • man 1 printenv (FreeBSD)

env is POSIX 7, printenv is not (GNU Coreutils in Ubuntu 15.10).

Related Solutions

Joining bash arguments into single string with spaces

[*] I believe that this does what you want. It will put all the arguments in one string, separated by spaces, with single quotes around all: str="'$*'" $* produces all the scripts arguments separated by the first character of $IFS which, by default, is a space....

AddTransient, AddScoped and AddSingleton Services Differences

TL;DR Transient objects are always different; a new instance is provided to every controller and every service. Scoped objects are the same within a request, but different across different requests. Singleton objects are the same for every object and every...

How to download package not install it with apt-get command?

Use --download-only: sudo apt-get install --download-only pppoe This will download pppoe and any dependencies you need, and place them in /var/cache/apt/archives. That way a subsequent apt-get install pppoe will be able to complete without any extra downloads....

What defines the maximum size for a command single argument?

Answers Definitely not a bug. The parameter which defines the maximum size for one argument is MAX_ARG_STRLEN. There is no documentation for this parameter other than the comments in binfmts.h: /* * These are the maximum length and maximum number of strings...

Bulk rename, change prefix

I'd say the simplest it to just use the rename command which is common on many Linux distributions. There are two common versions of this command so check its man page to find which one you have: ## rename from Perl (common in Debian systems -- Ubuntu, Mint,...

Output from ls has newlines but displays on a single line. Why?

When you pipe the output, ls acts differently. This fact is hidden away in the info documentation: If standard output is a terminal, the output is in columns (sorted vertically) and control characters are output as question marks; otherwise, the output is...

mv: Move file only if destination does not exist

mv -vn file1 file2. This command will do what you want. You can skip -v if you want. -v makes it verbose - mv will tell you that it moved file if it moves it(useful, since there is possibility that file will not be moved) -n moves only if file2 does not exist....

Is it possible to store and query JSON in SQLite?

SQLite 3.9 introduced a new extension (JSON1) that allows you to easily work with JSON data . Also, it introduced support for indexes on expressions, which (in my understanding) should allow you to define indexes on your JSON data as well. PostgreSQL has some...

Combining tail && journalctl

You could use: journalctl -u service-name -f -f, --follow Show only the most recent journal entries, and continuously print new entries as they are appended to the journal. Here I've added "service-name" to distinguish this answer from others; you substitute...

how can shellshock be exploited over SSH?

One example where this can be exploited is on servers with an authorized_keys forced command. When adding an entry to ~/.ssh/authorized_keys, you can prefix the line with command="foo" to force foo to be run any time that ssh public key is used. With this...

Why doesn’t the tilde (~) expand inside double quotes?

The reason, because inside double quotes, tilde ~ has no special meaning, it's treated as literal. POSIX defines Double-Quotes as: Enclosing characters in double-quotes ( "" ) shall preserve the literal value of all characters within the double-quotes, with the...

What is GNU Info for?

GNU Info was designed to offer documentation that was comprehensive, hyperlinked, and possible to output to multiple formats. Man pages were available, and they were great at providing printed output. However, they were designed such that each man page had a...

Set systemd service to execute after fstab mount

a CIFS network location is mounted via /etc/fstab to /mnt/ on boot-up. No, it is not. Get this right, and the rest falls into place naturally. The mount is handled by a (generated) systemd mount unit that will be named something like mnt-wibble.mount. You can...

Merge two video clips into one, placing them next to each other

To be honest, using the accepted answer resulted in a lot of dropped frames for me. However, using the hstack filter_complex produced perfectly fluid output: ffmpeg -i left.mp4 -i right.mp4 -filter_complex hstack output.mp4 ffmpeg -i input1.mp4 -i input2.mp4...

How portable are /dev/stdin, /dev/stdout and /dev/stderr?

It's been available on Linux back into its prehistory. It is not POSIX, although many actual shells (including AT&T ksh and bash) will simulate it if it's not present in the OS; note that this simulation only works at the shell level (i.e. redirection or...

How can I increase the number of inodes in an ext4 filesystem?

It seems that you have a lot more files than normal expectation. I don't know whether there is a solution to change the inode table size dynamically. I'm afraid that you need to back-up your data, and create new filesystem, and restore your data. To create new...

Why doesn’t cp have a progress bar like wget?

The tradition in unix tools is to display messages only if something goes wrong. I think this is both for design and practical reasons. The design is intended to make it obvious when something goes wrong: you get an error message, and it's not drowned in...