mbigras
mbigras

Reputation: 8055

How can I get octal file permissions with Ruby?

I read another answer for how to get the octal file permissions using perl:

$ perl -e 'printf "%04o %s\n", (stat)[2] & 07777, $_ for @ARGV' *.txt
0644 1.txt
0644 2.txt
0644 3.txt
0644 4.txt
0600 PerlOneLiner.txt
0664 perl.txt

So far I've used the File::Stat class and the #printf method. However, I'm getting a leading 100 on all my output.

$ ruby -e 'Dir["**/**"].each { |f| printf "%04o\t#{f}\n", File.stat(f).mode }'
100711  cplink
100644  hello_world.rb
100755  lso
100711  rename_images

Upvotes: 3

Views: 3040

Answers (3)

ribamar
ribamar

Reputation: 1495

It should be as simple as:

path = "file_with_0755"
File.stat(path).mode.to_s(8).split("")[-4..-1].join
# => "0755"
File.stat(path).mode.to_s(8).split("")[-4..-1].join.to_i(8) == 0755
# => true

Upvotes: 3

mu is too short
mu is too short

Reputation: 434945

If you check section two of your libc manual (man 2 stat from the shell) you should see something like this:

The status information word st_mode has the following bits:

#define S_IFMT   0170000  /* type of file */
#define S_IFIFO  0010000  /* named pipe (fifo) */
#define S_IFCHR  0020000  /* character special */
#define S_IFDIR  0040000  /* directory */
#define S_IFBLK  0060000  /* block special */
#define S_IFREG  0100000  /* regular */
#define S_IFLNK  0120000  /* symbolic link */
#define S_IFSOCK 0140000  /* socket */
#define S_IFWHT  0160000  /* whiteout */
#define S_ISUID  0004000  /* set user id on execution */
#define S_ISGID  0002000  /* set group id on execution */
#define S_ISVTX  0001000  /* save swapped text even after use */
#define S_IRUSR  0000400  /* read permission, owner */
#define S_IWUSR  0000200  /* write permission, owner */
#define S_IXUSR  0000100  /* execute/search permission, owner */

The precise contents won't be exactly the same but the octal values should be the same on any Unixy system.

The part you're interested in would be the "this is a regular file" bit:

#define S_IFREG  0100000  /* regular */

That's where your leading 100 comes from.

If you look back at the Perl version, you'll see that they're applying a bit mask:

(stat)[2] & 07777
          ^^^^^^^

to grab just the permission bits. If you do the same in Ruby:

printf "%04o\t#{f}\n", (File.stat(f).mode & 07777)
# ----------------------------------------^^^^^^^

you'll get the sort of output that you're expecting.


If you don't have libc man pages then you can look at the OpenGroup's stat documentation which will point you at the struct stat documentation which covers the various bits in the mode:

┌─────────┬───────────┬───────────────────────────────────────────┐
│ Name    │  Numeric  │               Description                 │            
│         │   Value   │                                           │            
├─────────┼───────────┼───────────────────────────────────────────┤
│ S_IRWXU │ 0700      │ Read, write, execute/search by owner.     │ 
├─────────┼───────────┼───────────────────────────────────────────┤
│ S_IRUSR │ 0400      │ Read permission, owner.                   │            
├─────────┼───────────┼───────────────────────────────────────────┤
│ S_IWUSR │ 0200      │ Write permission, owner.                  │            
├─────────┼───────────┼───────────────────────────────────────────┤
│ S_IXUSR │ 0100      │ Execute/search permission, owner.         │
├─────────┼───────────┼───────────────────────────────────────────┤
│ S_IRWXG │ 070       │ Read, write, execute/search by group.     │ 
├─────────┼───────────┼───────────────────────────────────────────┤
│ S_IRGRP │ 040       │ Read permission, group.                   │            
├─────────┼───────────┼───────────────────────────────────────────┤
│ S_IWGRP │ 020       │ Write permission, group.                  │            
├─────────┼───────────┼───────────────────────────────────────────┤
│ S_IXGRP │ 010       │ Execute/search permission, group.         │
├─────────┼───────────┼───────────────────────────────────────────┤
│ S_IRWXO │ 07        │ Read, write, execute/search by others.    │ 
├─────────┼───────────┼───────────────────────────────────────────┤
│ S_IROTH │ 04        │ Read permission, others.                  │            
├─────────┼───────────┼───────────────────────────────────────────┤
│ S_IWOTH │ 02        │ Write permission, others.                 │            
├─────────┼───────────┼───────────────────────────────────────────┤
│ S_IXOTH │ 01        │ Execute/search permission, others.        │
├─────────┼───────────┼───────────────────────────────────────────┤
│ S_ISUID │ 04000     │ Set-user-ID on execution.                 │            
├─────────┼───────────┼───────────────────────────────────────────┤
│ S_ISGID │ 02000     │ Set-group-ID on execution.                │
├─────────┼───────────┼───────────────────────────────────────────┤
│ S_ISVTX │ 01000     │ On directories, restricted deletion flag. │
└─────────┴───────────┴───────────────────────────────────────────┘

Upvotes: 10

Jörg W Mittag
Jörg W Mittag

Reputation: 369594

What does the leading 100 mean given I'm on a macOS machine?

The return value of the File::Stat#mode method is platform-dependent, and apparently, that's what it returns on your platform.

In particular, the documentation says that for Unix machines, the definition from stat(2) is used, which on macOS is the following:

The status information word st_mode has the following bits:

#define S_IFMT 0170000           /* type of file */
#define        S_IFIFO  0010000  /* named pipe (fifo) */
#define        S_IFCHR  0020000  /* character special */
#define        S_IFDIR  0040000  /* directory */
#define        S_IFBLK  0060000  /* block special */
#define        S_IFREG  0100000  /* regular */
#define        S_IFLNK  0120000  /* symbolic link */
#define        S_IFSOCK 0140000  /* socket */
#define        S_IFWHT  0160000  /* whiteout */
#define S_ISUID 0004000  /* set user id on execution */
#define S_ISGID 0002000  /* set group id on execution */
#define S_ISVTX 0001000  /* save swapped text even after use */
#define S_IRUSR 0000400  /* read permission, owner */
#define S_IWUSR 0000200  /* write permission, owner */
#define S_IXUSR 0000100  /* execute/search permission, owner */

This matches up with the description in the Single Unix Specification, so it is more or less true for all Unices, not just macOS. (macOS has the additional "whiteout" file type, which is related to Time Machine, AFAIK, but that's okay, the SUS allows additional file types and permission bits.)

So, if I decipher that correctly, it means that hello_world.rb is

  • a regular file, not a fifo, character device, directory, block device, symlink, socket, or whiteout
  • not suid
  • not sgid
  • not sticky
  • readable and writeable, but not executable by its owner
  • readable, but not writeable or executable by the group
  • readable, but not writeable or executable by others

Why does my "%04o" not work?

%04o means "format as octal, minimum length 4, pad with zeroes if length is less than 4". And that's exactly what it does.

How do I achieve the same output as the linked perl script?

If you want to get the same output, you should do the same thing: the Perl script masks out the file type from the mode, if you do the same in Ruby, you should get the same result:

Dir["**/**"].each { |f| printf "%04o\t#{f}\n", File.stat(f).mode & 07777 }

Upvotes: 2

Related Questions