1

I am trying to understand how the ls command works, and I presume there is a shell script which defines ls somewhere in the filesystem. This correct? If so, where can I find it?

Owen
  • 569
  • 1
  • 5
  • 8
  • it isn't a shell script. Use the 'which' command to find an executable (that exists in the path), e.g. "which ls" – nod May 17 '16 at 07:27
  • 3
    @nod only use which if you're using tcsh or have aliased which to something like type -p or command -v. See http://unix.stackexchange.com/questions/85249/why-not-use-which-what-to-use-then – cas May 17 '16 at 09:35
  • 2
    Did you mean "source code" instead of "shell script" ? – Jeff Schaller May 17 '16 at 10:49
  • Perhaps someone once told you, "Everything in Linux is a shell script." Well, that is a false datum. – Wildcard May 17 '16 at 23:19
  • If instead of understanding you wanted to read it... https://github.com/wertarbyte/coreutils/blob/master/src/ls.c :-). You may find online the source code. HNY. – Hastur Dec 29 '21 at 10:56

3 Answers3

7

ls uses opendir() and readdir() to step through all the files in the directory. If it needs more information about one of them it calls stat(). Read the source of course, but a very handy shortcut is :

# strace ls

The key part with a couple comments is :

Get the directory entries

open(".", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_DIRECTORY|O_CLOEXEC) = 3
fcntl64(3, F_GETFD)                     = 0x1 (flags FD_CLOEXEC)
getdents64(3, /* 53 entries */, 32768)  = 1744
getdents64(3, /* 0 entries */, 32768)   = 0
close(3)                                = 0

Verify stdout is a character device

fstat64(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 0), ...}) = 0

map stdin into memory. (Not sure why, see the source)

mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1,
0) = 0xb73ff000

write the directory entries to stdout and wrap-up

write(1, "bin  Desktop  Documents  Downloa"..., 91bin  Desktop
Documents  Download  Music  Pictures  Public  public_html  Templates
Videos
) = 91
close(1)                                = 0
munmap(0xb73ff000, 4096)                = 0
close(2)                                = 0
exit_group(0)                           = ?
Rahul
  • 13,589
3

ls is not a shell script, if you issue file command, you will know it's an ELF 64-bit LSB executable file:

$ type -a ls
ls is aliased to `ls --color=auto'
ls is /usr/bin/ls #<---- now we know the file path of `ls`
ls is /bin/ls
$ 
$ file /usr/bin/ls
/usr/bin/ls: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=ddf8cdb3f1fd2e8263637b7c8ccea84fbf41ee3c, stripped
$ 

You can found the online source code here.

Or if your distro is RPM-based Linux distributions + dnf, then you can:

$ rpm -qf /usr/bin/ls
coreutils-8.22-22.fc21.x86_64 #so now we know the package name is coreutils
$ sudo dnf whatprovides /usr/bin/ls #alternative way
Using metadata from Mon May 16 02:39:55 2016 (1 day, 23:03:50 hours old)
coreutils-8.22-22.fc21.x86_64 : A set of basic GNU tools commonly used in shell scripts
Repo        : @System

coreutils-8.22-19.fc21.x86_64 : A set of basic GNU tools commonly used in shell scripts
Repo        : fedora

coreutils-8.22-22.fc21.x86_64 : A set of basic GNU tools commonly used in shell scripts
Repo        : updates

$ 
$ mkdir coreutils #optional
$ cd coreutils #optional
$ sudo dnf download --source coreutils
...
$ rpm2cpio coreutils-8.22-22.fc21.src.rpm |cpio -idmv
...
$ sudo rm coreutils-8.22-22.fc21.src.rpm #optional
$ unp coreutils-8.22.tar.xz
...
$ rm coreutils-8.22.tar.xz #optional
$ cd coreutils-8.22/
$ find . -iname 'ls*'
./lib/lseek.c
./lib/lstat.c
./src/ls.c  #<---- now we know ls.c is here
./src/ls-vdir.c
./src/ls.h
./src/ls-ls.c
./src/ls-dir.c
./man/ls.x
./tests/ls
./tests/misc/ls-misc.pl
./tests/misc/ls-time.sh
./m4/ls-mntd-fs.m4
./m4/lstat.m4
./m4/lseek.m4
$ vi ./src/ls.c

Note:

  1. coreutils-8.22-22.fc21.src.rpm is mine, your package number may vary.

  2. Some commands like type -a history return "history is a shell builtin", you should look at the current shell source code, i.e. rpm -qf `readlink -f /proc/$$/exe` (Detect current shell by command is tricky than you might think, this trick doesn't work in fish shell)

  3. In csh/tcsh shell, you should use where history because no such type command. More details can be found here.

  4. You might also interest to try wildcard, e.g. repoquery --resolve --archlist=src '*compress*' to include not installed packages (Be careful if query command like '*uncompress*', in this case you need to remove the prefix 'un' to narrow down if first attempt '*uncompress*' failed). The output from repoquery above need to remove middle 0: and optionally postfix with .rpm to get the correct full name which you can used to search in http://rpm.pbone.net , e.g. ncompress-0:4.2.4.4-3.fc21.src change it to ncompress-4.2.4.4-3.fc21.src.rpm

  5. You can enable mirror debugging when dnf download source, in case mirror server down. See this.

[UPDATE]

In case you have rpm error due to invalid repos like me, this is how i fixed it:

$ sudo dnf config-manager --set-enabled '*' #Enable all repos, at anytime, check with `sudo dnf repolist all`
$ repoquery --resolve --archlist=src '*compress*'                                       
Could not match packages: failure: repodata/repomd.xml from rpmfusion-free-rawhide-source: [Errno 256] No more mirrors to try.
http://free.nchc.org.tw/rpmfusion/free/fedora/development/rawhide/source/SRPMS/repodata/repomd.xml: [Errno 14] HTTP Error 404 - Not Found                               
...
$ repoquery --resolve --archlist=src --enablerepo='*source' --disablerepo='rpmfusion-free-rawhide-source'  '*compress*' #not works too
...
$ sudo yum-config-manager --save --disablerepo=rpmfusion-nonfree-rawhide-source #for unknown reason, it doesn't work
$ sudo dnf config-manager --set-disabled rpmfusion-free-rawhide-source #for unknown reason, it doesn't work
$ grep -rnIH -D skip --color=always rpmfusion-free-rawhide-source /etc/yum.repos.d/
/etc/yum.repos.d/rpmfusion-free-rawhide.repo:17:[rpmfusion-free-rawhide-source]
$ sudo vi /etc/yum.repos.d/rpmfusion-free-rawhide.repo #Edit rpmfusion-free-rawhide-source from enabled=1 to enabled=0
$ repoquery --resolve --archlist=src  '*compress*'#now should works :) repeat the `grep and vi` steps above if got error in other repos, in my case i have to disable rpmfusion-nonfree-rawhide-source too.

p/s: Edit the title from [rpmfusion-free-rawhide-source] to [rpmfusion-free-rawhide-sourceDISABLE] hack should make --enablerepo='*source' works, though so far i found it's unnecessary because i already enable all repos in first command.

林果皞
  • 5,156
  • 3
  • 33
  • 46
1

I dont know if that is a answer because ls is written in C, but you can write a shell script to do a "ls" using the for loop:

for f in *;do echo $f; done

It is also usefull in some static shells...