1

i had the follow structure of Folder/File, for example

./3DO Company, The - 3DO/goldstar.bin
./3DO Company, The - 3DO/panafz10e-anvil.bin
./Arcade/pgm.zip
./Arcade/skns.zip
./Atari - 400-800/ATARIBAS.ROM
./Atari - 5200/5200.rom
./Atari - 7800/7800 BIOS (E).rom
./Atari - Lynx/lynxboot.img
./Coleco - ColecoVision/coleco.rom

that is just for star arrange the folder/files but i need symbolic links to each file on all sub-folders (not copy, just thing i not have enough free space for make copies) but i need full name of file including special characters and just using releative path, not the absolute path.

well i know using a loop, like "for" is possible, i had the idea but i don't have a correct command for do this under bash in GNU/Linux.

the result i searching is :

./goldstar.bin
./panafz10e-anvil.bin
./pgm.zip
./skns.zip
./ATARIBAS.ROM
./5200.rom
./7800 BIOS (E).rom
./lynxboot.img
./coleco.rom

Someone can help me with this ?

inukaze
  • 113

1 Answers1

0

With find, basic solution:

# remember to cd to the right directory first
find . -exec ln -s {} ./ \;

Caveats:

  • The basic solution will try to create symbolic links to all files, including files of the type directory; you probably want links to regular files only, so -type f is a good idea.

  • Attempts to link to files directly under . and to . itself will fail (file exists) because the paths are taken (edge case: if such file is removed after find spots it but before ln acts then a symbolic link leading to itself will be created; compare TOCTOU). With GNU find -mindepth 2 allows you to skip these files; posixly (portably) you can check if there are at least two slashes in the path: -path './*/*'.

The improved command is:

find . -type f -path './*/*' -exec ln -s {} ./ \;

This is still sub-optimal because the command runs a separate ln for each matching file. If your ln supports -t then -exec ln -s -t ./ {} + will work. A portable improvement is:

find . -type f -path './*/*' -exec sh -c 'ln -s "$@" ./' find-sh {} +

The point is -exec … {} + can process many pathnames at once, but {} must be just before +, so -exec ln -s {} ./ + would not work. In shell code $@ can be anywhere. By using a shell we can make one portable ls handle multiple pathnames passed by find. find-sh is explained here: What is the second sh in sh -c 'some shell code' sh?

If a symbolic link is going to be created and the name is already taken in the current working directory then the link won't be created and the existing file won't be overwritten.

"Already taken" may refer to a symbolic link created a moment earlier. This means if there are regular files with the same name (obviously in different directories) then at most one symbolic link with the name will be created (expect file exists errors). The target of the symbolic link will be the first file of the bunch. In general you cannot easily predict the order (I think any(?) find uses the directory order, recursively), so you cannot easily guess which of the files with the same name will be linked to.

To observe what is being processed, add -print at the very end (ln -v is an alternative, not portable though). The final portable command is:

find . -type f -path './*/*' -exec sh -c 'ln -s "$@" ./' find-sh {} + -print

Note -exec … {} + is always evaluated as true, what it does cannot affect -print. If -print prints some pathname, it only means an attempt to crate a symbolic link was made; it does not mean the attempt succeeded. If you want to print only successfully linked pathnames then you need -exec ln -s {} ./ \; -print. This will work because -exec … \; is evaluated as true or false according to the exit status of the command inside, so if ln fails then -print will not print the pathname.