Convert ls -l output format to chmod format

Some systems have commands to display the permissions of a file as a number, but unfortunately, nothing portable.

zsh has a stat (aka zstat) builtin in the stat module:

zmodload zsh/stat
stat -H s some-file

Then, the mode is in $s[mode] but is the mode, that is type + perms.

If you want the permissions expressed in octal, you need:

perms=$(([##8] s[mode] & 8#7777))

BSDs (including Apple OS/X) have a stat command as well.

mode=$(stat -f %p some-file)
perm=$(printf %o "$((mode & 07777))"

GNU find (from as far back as 1990 and probably before) can print the permissions as octal:

find some-file -prune -printf '%m\n'

Later (2001, long after zsh stat (1997) but before BSD stat (2002)) a GNU stat command was introduced with again a different syntax:

stat -c %a some-file

Long before those, IRIX already had a stat command (already there in IRIX 5.3 in 1994) with another syntax:

stat -qp some-file

Again, when there's no standard command, the best bet for portability is to use perl:

perl -e 'printf "%o\n", (stat shift)[2]&07777' some-file

You can ask GNU stat to output the permissions in octal format by using the -c option. From man stat:

       -c  --format=FORMAT
              use the specified FORMAT instead of the default; output a
              newline after each use of FORMAT
⋮
       %a     access rights in octal
⋮
       %n     file name

So in your case:

bash-4.2$ ls -l foo
-rw-r--r-- 1 manatwork manatwork 0 Apr  7 19:43 foo

bash-4.2$ stat -c '%a' foo
644

Or you can even automate it by formatting stat's output as valid command:

bash-4.2$ stat -c "chmod %a '%n'" foo
chmod 644 'foo'

bash-4.2$ stat -c "chmod %a '%n'" foo > setpermission.sh

bash-4.2$ chmod a= foo

bash-4.2$ ls -l foo
---------- 1 manatwork manatwork 0 Apr  7 19:43 foo

bash-4.2$ sh setpermission.sh 

bash-4.2$ ls -l foo
-rw-r--r-- 1 manatwork manatwork 0 Apr  7 19:43 foo

The above solution will also work for multiple files if using a wildcard:

stat -c "chmod -- %a '%n'" -- *

Will work correctly with file names containing whitespace characters, but will fail on file names containing single quotes.


To convert from the symbolic to octal notation, I once came up with:

chmod_format() {
  sed 's/.\(.........\).*/\1/
    h;y/rwsxtSTlL-/IIIIIOOOOO/;x;s/..\(.\)..\(.\)..\(.\)/|\1\2\3/
    y/sStTlLx-/IIIIIIOO/;G
    s/\n\(.*\)/\1;OOO0OOI1OIO2OII3IOO4IOI5IIO6III7/;:k
    s/|\(...\)\(.*;.*\1\(.\)\)/\3|\2/;tk
    s/^0*\(..*\)|.*/\1/;q'
}

Expanded:

#! /bin/sed -f
s/.\(.........\).*/\1/; # extract permissions and discard the rest

h; # store a copy on the hold space

# Now for the 3 lowest octal digits (rwx), translates the flags to
# binary where O means 0 and I means 1.
# l, L are for mandatory locking (a regular file that has 02000 on
# and not 010 on some systems like Linux). Some ls implementations
# like GNU ls confusingly use S there like for directories even though 
# it has nothing to do with setgid in that case. Some ls implementations 
# use L, some others l (against POSIX which requires an uppercase
# flag for extra flags when the execution bit is not set).
y/rwsxtSTlL-/IIIIIOOOOO/

x; # swap hold and pattern space, to do a second processing on those flags.

# now only consider the "xXlLsStT" bits:
s/..\(.\)..\(.\)..\(.\)/|\1\2\3/

y/sStTlLx-/IIIIIIOO/; # make up the 4th octal digit as binary like before

G; # append the hold space so we now have all 4 octal digits as binary

# remove the extra newline and append a translation table
s/\n\(.*\)/\1;OOO0OOI1OIO2OII3IOO4IOI5IIO6III7/

:k
  # translate the OOO -> 0 ... III -> 7 in a loop
  s/|\(...\)\(.*;.*\1\(.\)\)/\3|\2/
tk

# trim leading 0s and our translation table.
s/^0*\(..*\)|.*/\1/;q

That returns the octal number from the output of ls -l on one file.

$ echo 'drwSr-sr-T' | chmod_format
7654