0

ANSWER: My problem was that ${DIRSTACK[0]} holds a literal ~, while ${DIRSTACK[@]:1} will not. When I was calling sort, I was rearranging the order and then whichever DIRSTACK element ${DIRSTACK[0]} got moved to would not work with pushd +element. I solved this problem by not logging ${DIRSTACK[0]} in my $HOME/.logs/dirstack so that it will never be re-sorted to another index. This bug was reported in October 2017 and will be fixed in some version of bash >4.4.

ORIGINAL QUESTION

So I tried to write a pushd function that could 1) remove duplicates from pushd (since bash doesn't have dunique) and 2) keep the last 10 directories across all terminals, meaning I have to write to a file.

pd() {
    if [[ -f "$HOME/.logs/dirstack" ]]; then
        dirs -c
        s=($(sort "$HOME/.logs/dirstack" | uniq))
        for dir in "${s[@]}"; do
            pushd -n "$dir" >/dev/null
        done
    fi
    local choice="+$1"
    pushd $choice
    printf '%s\n' "${DIRSTACK[@]:0:10}" >"$HOME/.logs/dirstack"
}

So, obviously this function is dumb because with the sorting you really don't know what directory you're cding into, but this is closer to a Minimum, Viable, Complete Example to show the problem.

Most of the time, this function does not work, but sometimes it will. On failure, it displays an error like:

j@pc$ pd 2
-bash: pushd: ~/jenkins/docker: No such file or directory

On success, it will correctly to go that directory. Failure is independent of the particular directory used. All of the directories exist and I can pushd ~/jenkins/docker or cd ~/jenkins/docker without issue.

jeremysprofile
  • 6,648
  • 4
  • 25
  • 41
  • `~/jenkins/docker` is a problem because the `~` is literal; it's looking for a directory with a `~` character in its name. – Charles Duffy Jul 12 '18 at 21:19
  • ...beyond that, `s=( $( ... ) )` is itself a code smell, since it splits content on all characters in IFS. Any directory with spaces is going to become multiple array entries. And literal newlines are legal in directory names, so you aren't completely safe even if you set `IFS=$'\n'`. – Charles Duffy Jul 12 '18 at 21:19
  • The MCVE is `cd "~"` which shows `bash: cd: ~: No such file or directory` because `~` is not an actual directory but instead shell syntax. `dirs -l` will show directories with actual directory paths instead of `~`-notation. – that other guy Jul 12 '18 at 21:20
  • @thatotherguy, but it works sometimes, on file paths that include ~ – jeremysprofile Jul 12 '18 at 21:21
  • @jeremysprofile, very unlikely. Can you provide a reproducer for such a case? `var='~'; cd "$var"` will **always** fail unless you have a directory named `~`; see the linked duplicate. – Charles Duffy Jul 12 '18 at 21:22
  • @jeremysprofile, ...btw, using `set -x` to enable trace-level logging is likely to be helpful. – Charles Duffy Jul 12 '18 at 21:23
  • @CharlesDuffy, I'm never quoting ~ or the variable in this. `dirs -v` will show the ~, but `pushd ~` always works. I can get a picture for you? What reproducer would you like? – jeremysprofile Jul 12 '18 at 21:24
  • @jeremysprofile, if you don't quote the `~` on assignment **when it's entered as a value**, then what's assigned is not the literal value but its expansion result. But if it's part of a command substitution result, then it's never expanded, no matter what the quoting is. – Charles Duffy Jul 12 '18 at 21:25
  • @jeremysprofile, ...that is to say, `var='~'` behaves identically to `printTilde() { echo '~'; }; var=$(printTilde)`, even though the latter doesn't have quotes (just like your `$(sort ...)` isn't surrounded by quotes). – Charles Duffy Jul 12 '18 at 21:25
  • @jeremysprofile, ...a textual transcript (maybe copy-and-pasted into an [edit] to the question) taken after running `set -x` would be helpful, yes; ideally, one would show both an operation working, and an operation failing; showing such a transcript of a `cd` to a directory containing a `~` coming from a parameter expansion result would convince me that the dupe flag was incorrect. – Charles Duffy Jul 12 '18 at 21:26
  • @CharlesDuffy, sure, 5 minutes – jeremysprofile Jul 12 '18 at 21:28
  • BTW, replacing `s=( $(sort ...) )` with `readarray -t s < – Charles Duffy Jul 12 '18 at 21:36
  • @CharlesDuffy, added – jeremysprofile Jul 12 '18 at 21:37
  • ...so, `pushd -n` doesn't *actually* try to change directories, so it doesn't report the tilde as a failure, but the first time you actually enact a failure to a stack entry with a literal tilde in it, that fails. That's entirely consistent with the issue the linked duplicate is about. – Charles Duffy Jul 12 '18 at 21:38
  • notably, you only added one entry with a tilde, and that's the only one that fails. – Charles Duffy Jul 12 '18 at 21:38
  • (the `pushd +2` output shows tildes in the others, but only the failing one had a tilde present in your input/log file, and thus when `pushd -n` was called). – Charles Duffy Jul 12 '18 at 21:39
  • @CharlesDuffy, looks like you're right. I typed all of them with a tilde, though. Any chance you know why only some of them are converting a tilde to $HOME correctly? – jeremysprofile Jul 12 '18 at 21:40
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/174912/discussion-between-jeremysprofile-and-charles-duffy). – jeremysprofile Jul 12 '18 at 21:40
  • @CharlesDuffy, this bug was reported [October 2017](https://lists.gnu.org/archive/html/bug-bash/2017-10/msg00121.html). I was kind of excited to get to submit a bug report. Oh well. Thanks for your help. – jeremysprofile Jul 14 '18 at 23:07

0 Answers0