0

I'm trying to make a bash script that will give me a command that will take a number n as argument and change the directory to the nth directory listed by running ls in the working directory. Here's what I have:

#! /bin/bash -

arg=$(ls --group-directories-first | awk -v first=$1 'NR == first' | tr -d '[[:space:]]') cd $arg

It does not work. Nothing happens at all when I run it.

The first line does seem to be capturing the directory name in the way that I want. If I change the script to

#! /bin/bash -

arg=$(ls --group-directories-first | awk -v first=$1 'NR == first' | tr -d '[[:space:]]') echo $arg

and run it in a directory in which the first directory is bin, the script outputs 'bin' when I give it the argument 1:

[user@manjaro ~]$ ls
bin/        
Desktop/    
Documents/  
Downloads/  
[user@manjaro ~]$ cdn 1
bin

So why does cd not take 'bin' and change to bin when I use the original version? And how should I change it?

One final note. When I try to do something similar outside a script it works fine:

[user@manjaro ~]$ foo=bin
[user@manjaro ~]$ cd $foo
[user@manjaro bin]$ pwd
/home/user/bin

Again, why?

  • The current directory changes for the script, but not for the shell you invoke the script from. – choroba Apr 02 '21 at 17:08

1 Answers1

1

The working directory is a property of the running process, and your script runs in a separate process from the shell. So changing the directory in the script doesn't affect your interactive shell. And that's a good thing, otherwise something like find could leave your shell in whatever random directory, esp. if you were to interrupt them mid-work.

You could put pwd in the script after the cd to see that it indeed works, the working directory if the script changes. And then the script exits.

Make it a shell function instead:

cdn() {
    arg=$(ls --group-directories-first |
          awk -v first="$1" 'NR == first' | tr -d '[[:space:]]')
    cd -- "$arg"
}

(I'm not sure why you have the tr there.)

Or, using arrays in Bash:

cdn() {
    dirs=( ./*/ )
    if [[ ${#dirs[@]} == 0 ]]; then
        echo "No directories!" >&2
        return 1
    fi 
    # minus one to go from one-based to zero-based indexing
    cd -- "${dirs[$1 - 1]}"
}

The latter should be safer in case you have file names with newlines or spaces. (That doesn't check if a directory exists matching the given index. If it doesn't, it just runs cd '', "changing" to the current directory.)

ilkkachu
  • 138,973