1

I may know how to use readlink to get normal files from SymLinks. I had this very basic idea to replace any SymLink in $@ passed to my function as follows:

for file in "$@"; do
    [ -L "$file" ] && file=$(readlink -f "$file")
done

Example would be a SymLink /etc/os-release -> ../usr/lib/os-release.

But if I do this:

set -- '/etc/os-release'; for file in "$@"; do [ -L "$file" ] && file=$(readlink -f "$file"); done; echo "$@"

I still get the original SymLink path, which is .. disappointing. I was hoping to be able to directly modify $@, if not possible, any there any workaround?

2 Answers 2

4

Your original code:

for file in "$@"; do
    [ -L "$file" ] && file=$(readlink -f -- "$file")
done

The issue is that setting file in the loop has no consequence to the contents of the list of positional parameters.

Instead, you may want to use

for file in "$@"; do
    [ -L "$file" ] && file=$(readlink -f -- "$file")
    set -- "$@" "$file"
    shift
done

Now we're shifting off the entries from the start of "$@", one by one, and adding the possibly modified entries to the back of the list. Modifying $@ in a for loop over $@ is not an issue as for loops always iterates over a static list, i.e. the list that is iterated over is evaluated once at the start of the loop, only.

The loop above, like the ones below, resets the entire list of positional parameters in each iteration. For a huge list of filenames, this may soon become quite slow. If your shell has arrays, Stephen Kitt's answer shows one way to speed it up.

The loop could also be written in a slightly shorter form (calling readlink for each and every element),

for dummy do
    set -- "$@" "$(readlink -f -- "$1")"
    shift
done

This uses readlink as per your question, even though that's not a POSIX utility.

Note that if the output of readlink ends with one or several newlines (beside the one added to terminate the last line of output), these would be stripped off. This is a consequence of the command substitution. If this is an issue, add a trailing character artificially in the command substitution, and then remove it, as mosvy suggests in comments:

for file do
    [ -L "$file" ] && file=$(readlink -f -- "$file"; echo x)
    set -- "$@" "${file%x}"
    shift
done
5
  • @LinuxSecurityFreak That would only be needed if you, like he suggests, call readlink with a list of arguments. The set -f is needed together with setting IFS to a newline, to split the result of such a call to readlink into multiple filenames. I did not do that here. Note that doing so would disqualify filenames that contains embedded newlines (which is uncommon to have, but allowed).
    – Kusalananda
    Commented Mar 7, 2020 at 9:14
  • @LinuxSecurityFreak That may be a good question. Consider doing a few searches on the site first to see if the topic has been covered before first.
    – Kusalananda
    Commented Mar 7, 2020 at 9:20
  • Ok, will do later on. Right now heading offline. Thanks again. Commented Mar 7, 2020 at 9:21
  • 1
    You can work around the stripping of newlines via r=$(readlink -f "$1"; echo x); set -- "$@" "${r%x}".
    – user313992
    Commented Mar 7, 2020 at 9:39
  • @StéphaneChazelas Ouch. Thanks. I fixed that and the last loop too. Had too much other things to think about this morning...
    – Kusalananda
    Commented Mar 7, 2020 at 14:38
0

Note that neither readlink nor realpath are POSIX commands. The POSIX command line toolchest has no realpath()-like API. Here, instead of readlink (of which there exists different incompatible implementations), you could use zsh which also has a realpath()-like functionality built-in with its :P modifier. In your sh script:

eval "set -- $(zsh -c 'print -r ${(qq)argv:P}' zsh "$@")"

Though you might as well write your script in zsh in the first place, where it would just be:

argv=($argv:P)

Or if python3 is more likely to be available than zsh:

eval "set -- $(
  LC_ALL=C python3 -c '
import sys, os, shlex
print(" ".join(map(shlex.quote, map(os.path.realpath, sys.argv[1:]))))' "$@")"

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.