Say I have the following output from ls -l
:
drwxr-xr-x 2 root root 4096 Apr 7 17:21 foo
How can I automatically convert this to the format used by chmod
?
For example:
$ echo drwxr-xr-x | chmod-format
755
I'm using OS X 10.8.3.
Say I have the following output from ls -l
:
drwxr-xr-x 2 root root 4096 Apr 7 17:21 foo
How can I automatically convert this to the format used by chmod
?
For example:
$ echo drwxr-xr-x | chmod-format
755
I'm using OS X 10.8.3.
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.
stat -f %Lp some-file
(without the L
, the full mode is returned, in octal)
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.
stat -f 'chmod %p "%N"'
– gelraen
Apr 08 '13 at 13:56
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
dpkg
to set permissions back to "as installed". Thank you for answering the literal question without regard to which command produced the permission string.
– HiTechHiTouch
Feb 08 '17 at 13:44
This command on Mac under sh
stat -f "%Lp %N" your_files
if you only want the numeric permission, use %Lp only.
for example:
stat -f "%Lp %N" ~/Desktop
700 Desktop
The 700 is the numeric permission which can be used in chmod, and Desktop is the filename.
Here's an answer to question Y (ignoring question X), inspired by the OP's attempt:
#!/bin/bash
LC_COLLATE=C
while read ls_out
do
extra=0
perms=0
for i in {1..9}
do
# Shift $perms to the left one bit, so we can always just add the LSB.
let $((perms*=2))
this_char=${ls_out:i:1}
# If it's different from its upper case equivalent,
# it's a lower case letter, so the bit is set.
# Unless it's "l" (lower case L), which is special.
if [ "$this_char" != "${this_char^}" ] && [ "$this_char" != "l" ]
then
let $((perms++))
fi
# If it's not "r", "w", "x", or "-", it indicates that
# one of the high-order (S/s=4000, S/s/L/l=2000, or T/t=1000) bits
# is set.
case "$this_char" in
([^rwx-])
let $((extra += 2 ** (3-i/3) ))
esac
done
printf "%o%.3o\n" "$extra" "$perms"
done
The above contains a few bashisms. The following version seems to be POSIX-compliant:
#!/bin/sh
LC_COLLATE=C
while read ls_out
do
extra=0
perms=0
for i in $(seq 1 9)
do
# Shift $perms to the left one bit, so we can always just add the LSB.
: $((perms*=2))
this_char=$(expr "$ls_out" : ".\{$i\}\(.\)")
# Lower case letters other than "l" indicate that permission bits are set.
# If it's not "r", "w", "x", or "-", it indicates that
case "$this_char" in
(l)
;;
([a-z])
: $((perms+=1))
esac
# If it's not "r", "w", "x", or "-", it indicates that
# one of the high-order (S/s=4000, S/s/L/l=2000, or T/t=1000) bits
# is set.
case "$this_char" in
([!rwx-])
: $((extra += 1 << (3-i/3) ))
esac
done
printf "%o%.3o\n" "$extra" "$perms"
done
Notes:
LC_COLLATE=C
tells the shell to treat letter sequence range patterns
as using the ASCII order, so [a-e]
is equivalent to [abcde]
.
In some locales (e.g., en_US), [a-e]
is equivalent to [aAbBcCdDeE]
(i.e., [abcdeABCDE]
) or perhaps [abcdeABCD]
—
see Why is the bash case statement not case-sensitive …?)In the second version (the POSIX-compliant one):
The first case
statement could be rewritten:
case "$this_char" in
([a-km-z])
: $((perms+=1))
esac
but I think the way I have it now makes it easier to see that l
is the letter that's being handled differently.
Alternatively, it could be rewritten:
case "$this_char" in
([rwxst])
: $((perms+=1))
esac
since r
, w
, x
, s
, and t
are the only letters
that should ever appear in a mode string (other than l
).
The second case
statement could be rewritten:
case "$this_char" in
([rwx])
;;
([A-Za-z])
: $((extra += 1 << (3-i/3) ))
esac
to enforce the rule that only letters are valid for specifying mode bits.
(By contrast, the more succinct version in the full script is lazy,
and will accept -rw@rw#rw%
as equivalent to rwSrwSrwT
.)
Alternatively, it could be rewritten:
case "$this_char" in
([SsTtLl])
: $((extra += 1 << (3-i/3) ))
esac
since S
, s
, T
, t
, L
, and l
are the only letters
that should ever appear in a mode string (other than r
, w
, and x
).
Usage:
$ echo drwxr-xr-x | chmod-format
0755
$ echo -rwsr-sr-x | chmod-format
6755
$ echo -rwSr-Sr-- | chmod-format
6644
$ echo -rw-r-lr-- | chmod-format
2644
$ echo ---------- | chmod-format
0000
And, yes, I know it's better not to use echo
with text that might begin
with -
; I just wanted to copy the usage example from the question.
Note, obviously, that this ignores the 0th character
(i.e., the leading d
/b
/c
/-
/l
/p
/s
/D
)
and the 10th (+
/.
/@
).
It assumes that the maintainers of ls
will never define
r
/R
or w
/W
as valid characters in the third, sixth, or ninth position
(and, if they do, they should be beaten with sticks).
Also, I just found the following code, by cas, under How to restore default group/user ownership of all files under /var:
let perms=0
[[ "${string}" = ?r???????? ]] && perms=$(( perms + 400 ))
[[ "${string}" = ??w??????? ]] && perms=$(( perms + 200 ))
[[ "${string}" = ???x?????? ]] && perms=$(( perms + 100 ))
[[ "${string}" = ???s?????? ]] && perms=$(( perms + 4100 ))
[[ "${string}" = ???S?????? ]] && perms=$(( perms + 4000 ))
[[ "${string}" = ????r????? ]] && perms=$(( perms + 40 ))
[[ "${string}" = ?????w???? ]] && perms=$(( perms + 20 ))
[[ "${string}" = ??????x??? ]] && perms=$(( perms + 10 ))
[[ "${string}" = ??????s??? ]] && perms=$(( perms + 2010 ))
[[ "${string}" = ??????S??? ]] && perms=$(( perms + 2000 ))
[[ "${string}" = ???????r?? ]] && perms=$(( perms + 4 ))
[[ "${string}" = ????????w? ]] && perms=$(( perms + 2 ))
[[ "${string}" = ?????????x ]] && perms=$(( perms + 1 ))
[[ "${string}" = ?????????t ]] && perms=$(( perms + 1001 ))
[[ "${string}" = ?????????T ]] && perms=$(( perms + 1000 ))
I have tested this code (but not thoroughly), and it seems to work,
except for the fact that it doesn't recognize l
or L
in the sixth position.
Note, though,
that while this answer is superior in terms of simplicity and clarity,
mine is actually shorter (counting only the code inside the loop;
the code that handles a single -rwxrwxrwx
string, not counting comments),
and it could be made even shorter
by replacing if condition; then …
with condition && …
.
Of course, you should not parse the output of ls
.
#!/bin/sh
and then used a few bashisms. Oops. But you missed a couple: $((
variable++
))
and $((
number
**
number
))
don’t seem to be POSIX either (the Standard doesn’t mention **
at all, and is squirrelly on ++
and --
). Conversely, *I* didn’t use [[…]]
; that appears only in cas’s answer, which I quoted from here. Also, my answer does handle ‘l’ and ‘L’, and I already pointed out the fact that cas’s answer doesn’t.
– Scott - Слава Україні
Jul 23 '15 at 01:57
$((- -a))
if you want a double negation, not that you may use $((--a))
to mean a decrement operation.
– Stéphane Chazelas
Jul 23 '15 at 06:39
seq
is not a POSIX command. You may be able to use the ${var#?} operator to avoid expr. Not that LC_COLLATE will not override LC_ALL
– Stéphane Chazelas
Jul 23 '15 at 06:43
8
's or 9
's, and no way to get more than 7
in any decimal position, he can pull of the charade. … … … … … … … … (This comment is a response to a Stéphane Chazelas comment that disappeared.)
– Scott - Слава Україні
Jul 23 '15 at 06:53
expr -rwsr-sr-x : ".\{$i\}\(.\)"
doesn't work on FreeBSD (invalid option), while expr -- -rwsr-sr-x : ".\{$i\}\(.\)"
won't work with busybox. So you'd need expr "x$var" : "x.\{$i\}\(.\)"
– Stéphane Chazelas
Jul 23 '15 at 08:19
An alternative, if you want to save the permissions away, to restore them later on, or on a different file is to use setfacl/getfacl
, and it will also restore (POSIX-draft) ACLs as a bonus.
getfacl some-file > saved-perms
setfacl -M saved-perms some-other-file
(on Solaris, use -f
instead of -M
).
However, though they are available on some BSDs, they are not on Apple OS/X where the ACLs are manipulated with chmod
only.
If your goal is to take permissions from one file and give them to another as well, GNU chmod
already has a "reference" option for that.
chmod
will not be the GNU chmod
there.
– Stéphane Chazelas
Apr 07 '13 at 21:59
On Mac OS X (10.6.8) you have to use stat -f format
(because it is actually NetBSD / FreeBSD stat
).
# using Bash
mods="$(stat -f "%p" ~)" # octal notation
mods="${mods: -4}"
echo "$mods"
mods="$(stat -f "%Sp" ~)" # symbolic notation
mods="${mods: -9}"
echo "$mods"
To just translate a symbolic permission string produced by ls -l
into octal (using only shell builtins) see: showperm.bash.
# from: showperm.bash
# usage: showperm modestring
#
# example: showperm '-rwsr-x--x'
stat
. Do you have it? (It's a GNU tool, so mostly available on Linux, not on Unix.) – manatwork Apr 07 '13 at 15:55stat foo
gives16777219 377266 drwxr-xr-x 119 Tyilo staff 0 4046 "Apr 7 17:49:03 2013" "Apr 7 18:08:31 2013" "Apr 7 18:08:31 2013" "Nov 25 17:13:52 2012" 4096 0 0 /Users/Tyilo
. I don't see755
in it. – Tyilo Apr 07 '13 at 16:08