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.
8 Answers 8
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
-
Perl-command seems to do the trick. Thanks for that.Jari Turkia– Jari Turkia2022年08月14日 10:33:01 +00:00Commented Aug 14, 2022 at 10:33
-
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.
-
2My
stat
doesn't have a-c
option. I'm using OS X 10.8.3.Tyilo– Tyilo2013年04月07日 17:15:04 +00:00Commented Apr 7, 2013 at 17:15 -
Thanks for the information, @Tyilo. And sorry, I can not help with OS X's tools.manatwork– manatwork2013年04月08日 06:25:25 +00:00Commented Apr 8, 2013 at 6:25
-
Try reading manpage^W^W^W stat(1) on Mac OS X have -f flag for specifying output format, e.g.
stat -f 'chmod %p "%N"'
imax– imax2013年04月08日 13:56:18 +00:00Commented Apr 8, 2013 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
-
I used this on the output of
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– HiTechHiTouch2017年02月08日 13:44:43 +00:00Commented Feb 8, 2017 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:
- The
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
, andt
are the only letters that should ever appear in a mode string (other thanl
).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 torwSrwSrwT
.) Alternatively, it could be rewritten:case "$this_char" in ([SsTtLl]) : $((extra += 1 << (3-i/3) )) esac
since
S
,s
,T
,t
,L
, andl
are the only letters that should ever appear in a mode string (other thanr
,w
, andx
).
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
.
-
@StéphaneChazelas: OK, I said
#!/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 - Слава Україні– Scott - Слава Україні2015年07月23日 01:57:19 +00:00Commented Jul 23, 2015 at 1:57 -
Sorry about l/L [[, I read too quickly. Yes, -- and ++ are not POSIX. POSIX allows shells to implement them, that means you have to write
$((- -a))
if you want a double negation, not that you may use$((--a))
to mean a decrement operation.Stéphane Chazelas– Stéphane Chazelas2015年07月23日 06:39:56 +00:00Commented Jul 23, 2015 at 6:39 -
Note that
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_ALLStéphane Chazelas– Stéphane Chazelas2015年07月23日 06:43:06 +00:00Commented Jul 23, 2015 at 6:43 -
@StéphaneChazelas: OK, you're talking about cas's answer again now, right? He's taking the "lazy" approach of using decimal arithmetic to construct a string that looks like an octal number. Note that all his step values (4000, 2000, 1000, 400, 200, 100, 40, 20, and 10) are decimal numbers. But, since there are no
8
's or9
's, and no way to get more than7
in any decimal position, he can pull of the charade. ... ... ... ... ... ... ... ... (This comment is a response to a Stéphane Chazelas comment that disappeared.)Scott - Слава Україні– Scott - Слава Україні2015年07月23日 06:53:49 +00:00Commented Jul 23, 2015 at 6:53 -
Yes, I realised that later, which is why I deleted the comment.Stéphane Chazelas– Stéphane Chazelas2015年07月23日 07:17:59 +00:00Commented Jul 23, 2015 at 7:17
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.
-
The OP mentioned he was on Apple OS/X, so
chmod
will not be the GNUchmod
there.Stéphane Chazelas– Stéphane Chazelas2013年04月07日 21:59:50 +00:00Commented Apr 7, 2013 at 21:59 -
Ah yeah I'm seeing the comment on the other answer where they say their platform. Mine isn't the only one mentioning GNU though and you can get GNU Utilities on Mac OS XBratchley– Bratchley2013年04月07日 22:27:15 +00:00Commented Apr 7, 2013 at 22:27
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.
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.)stat 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.