将 ls -l 输出格式转换为 chmod 格式

将 ls -l 输出格式转换为 chmod 格式

假设我有以下输出ls -l

drwxr-xr-x 2 root root 4096 Apr  7 17:21 foo

如何自动将其转换为 所使用的格式chmod

例如:

$ echo drwxr-xr-x | chmod-format
755

我使用的是 OS X 10.8.3。

答案1

有些系统有命令将文件的权限显示为数字,但不幸的是,没有任何可移植的。

zsh模块中有一个stat(又名)内置函数:zstatstat

zmodload zsh/stat
stat -H s some-file

那么,modeis中的$s[mode]but是模式,即type + perms。

如果您想要以八进制表示的权限,您需要:

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

BSD(包括苹果操作系统/Xstat)也有一个命令。

stat -f %Lp some-file

(没有L,完整的模式返回,以八进制表示)

GNU find(最早可以追溯到 1990 年,可能更早)可以将权限打印为八进制:

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

后来(2001 年,在zsh stat(1997 年)很久之后,但在 BSD stat(2002 年)之前),GNUstat命令再次引入了不同的语法:

stat -c %a some-file

早在这些之前,IRIX 就已经有了一个stat命令(已经存在于IRIX 5.31994 年),使用另一种语法:

stat -qp some-file

同样,当没有标准命令时,可移植性的最佳选择是使用perl

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

答案2

您可以stat使用该选项要求 GNU 以八进制格式输出权限-c。从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

所以在你的情况下:

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

stat或者您甚至可以通过将 的输出格式化为有效命令来自动化它:

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

如果使用通配符,上述解决方案也适用于多个文件:

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

对于包含空格字符的文件名可以正常工作,但对于包含单引号的文件名将失败。

答案3

为了从符号转换为八进制表示法,我一旦出现和:

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'
}

扩展:

#! /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

ls -l这会从一个文件的输出中返回八进制数。

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

答案4

这是问题的答案(忽略问题X),受到OP尝试的启发:

#!/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

以上内容包含一些攻击性内容。以下版本似乎符合 POSIX 标准:

#!/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

笔记:

  • 告诉LC_COLLATE=Cshell 将字母序列范围模式视为使用 ASCII 顺序,因此[a-e]相当于[abcde]。在某些语言环境中(例如 en_US),[a-e]相当于[aAbBcCdDeE] (ie, [abcdeABCDE]) 或者也许[abcdeABCD]— 请参阅为什么 bash case 语句不区分大小写……?
  • 在第二个版本(符合 POSIX 的版本)中:

    • 第一条case语句可以重写:

              case "$this_char" in
                ([a-km-z])
                      : $((perms+=1))
              esac
      

      但我认为现在的方式可以更容易地看出这l 封信正在以不同的方式处理。或者,可以重写:

              case "$this_char" in
                ([rwxst])
                      : $((perms+=1))
              esac
      

      因为rwxst是唯一应该出现在模式字符串中的字母(除了l)。

    • 第二条case语句可以重写:

              case "$this_char" in
                ([rwx])
                      ;;
                ([A-Za-z])
                      : $((extra += 1 << (3-i/3) ))
               esac
      

      强制执行只有字母对于指定模式位有效的规则。 (相比之下,完整脚本中更简洁的版本是惰性的,并且将接受-rw@rw#rw%等同于 rwSrwSrwT。)或者,可以重写它:

              case "$this_char" in
                ([SsTtLl])
                      : $((extra += 1 << (3-i/3) ))
              esac
      

      因为S, s, T, t, L, 和l是唯一应该出现在模式字符串中的字母(r, w, 和除外x)。

用法:

$ 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

而且,是的,我知道最好不要使用echo可能以-;开头的文本。我只是想复制问题中的用法示例。请注意,显然,这会忽略第 0 个字符(即前导d// b/ c/ -/ l/ p/ )和第 10 个字符 ( / / )。它假设 的维护者永远不会将 /或/定义为第三、第六或第九位置的有效字符(并且,如果他们这样做,他们应该是sD+.@lsrRwW用棍棒殴打)。


另外,我刚刚找到了以下代码,通过CAS, 在下面 如何恢复 /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 ))

我已经测试了这段代码(但没有彻底),它似乎可以工作,除了它无法识别lL位于第六位置这一事实。但请注意,虽然这个答案在简单性和清晰度方面更出色,但我的答案实际上更短(仅计算代码里面循环;处理单个字符串的代码,不包括注释),并且可以通过替换 为-rwxrwxrwx使其变得更短。if condition; then …condition && …


当然,你不应该解析输出ls

相关内容