Compare commits
10 commits
6a0425cafe
...
9c34a31fde
| Author | SHA1 | Date | |
|---|---|---|---|
| 9c34a31fde | |||
| c45d3bda9a | |||
| b20e1286d6 | |||
| 18f16bae58 | |||
| b99b5bc3b7 | |||
| ed919acff7 | |||
| 03d4c62e61 | |||
| 5232b8fb3b | |||
| f871153f29 | |||
| 00f8ca515f |
6 changed files with 280 additions and 21 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,3 +1,2 @@
|
||||||
owl
|
|
||||||
testdata/
|
testdata/
|
||||||
owl.1*
|
dist/
|
||||||
|
|
|
||||||
18
Makefile
18
Makefile
|
|
@ -1,10 +1,26 @@
|
||||||
SOURCES=*.go
|
SOURCES=*.go
|
||||||
LINKER_FLAGS=-X main.OwlVersion=`git describe --tags --dirty`
|
LINKER_FLAGS=-X main.OwlVersion=`git describe --tags --dirty`
|
||||||
|
INSTALL_DIR=${HOME}/.local
|
||||||
|
|
||||||
owl: ${SOURCES}
|
owl: ${SOURCES}
|
||||||
go build -o owl -ldflags "${LINKER_FLAGS}" -- ${SOURCES}
|
go build -o owl -ldflags "${LINKER_FLAGS}" -- ${SOURCES}
|
||||||
|
|
||||||
owl.1: man.md
|
owl.1: man.md
|
||||||
pandoc -s --shift-heading-level-by=-1 --to=man man.md > owl.1
|
pandoc -s --shift-heading-level-by=-1 --to=man man.md > owl.1
|
||||||
|
|
||||||
build: owl owl.1
|
build: owl owl.1
|
||||||
|
|
||||||
test: *.go
|
test: *.go
|
||||||
go test -v .
|
go test -v .
|
||||||
.PHONY: build test
|
|
||||||
|
install:
|
||||||
|
install -m 0755 -D owl ${INSTALL_DIR}/bin/owl
|
||||||
|
install -m 0644 -D owl.1 ${INSTALL_DIR}/man/man1/owl.1
|
||||||
|
install -m 0644 -D LICENSE ${INSTALL_DIR}/share/doc/LICENSE
|
||||||
|
|
||||||
|
uninstall:
|
||||||
|
rm -I ${INSTALL_DIR}/bin/owl \
|
||||||
|
${INSTALL_DIR}/man/man1/owl.1 \
|
||||||
|
${INSTALL_DIR}/share/doc/LICENSE
|
||||||
|
|
||||||
|
.PHONY: build test install uninstall
|
||||||
|
|
|
||||||
28
README.md
28
README.md
|
|
@ -24,6 +24,32 @@ appropriate - see `man.md` for usage and other important information.
|
||||||
|
|
||||||
See [Infrequently Asked Questions](./iaq.md) for unimportant information.
|
See [Infrequently Asked Questions](./iaq.md) for unimportant information.
|
||||||
|
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Firstly, bear in mind that any program which renames files could result in
|
||||||
|
data loss. In particular, while Owl has gone through some basic testing (see
|
||||||
|
<./test.bash> for details), from my own use on a large nested directory Owl
|
||||||
|
seems to miss some files, needing repeated use to rename each. This likely
|
||||||
|
indicates some kind of **severe bug**.
|
||||||
|
|
||||||
|
If you want to use Owl yourself, I would recommend one of the following:
|
||||||
|
1. Don't use the `--recurse` flag, only use on individual files
|
||||||
|
2. Use the `--dry-run` flag to find which files need renaming, but rename
|
||||||
|
files manually.
|
||||||
|
3. Contribute to Owl and help me fix the bug!
|
||||||
|
4. Use a different tool. A few alternatives are listed below, though I
|
||||||
|
haven't used any of them and so can't say whether they are good or not.
|
||||||
|
|
||||||
|
If you still want to install Owl after that, then do the following:
|
||||||
|
1. Install build dependencies (`make` and the `go` tool)
|
||||||
|
2. Clone this repository
|
||||||
|
3. Run `make build`
|
||||||
|
4. Run `make install`
|
||||||
|
Note the 3rd step won't work on windows, although you can likely just put
|
||||||
|
the executable (`./owl`) somewhere on your `PATH` environment variable and
|
||||||
|
it *should* work just the same.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
@ -37,3 +63,5 @@ See [Infrequently Asked Questions](./iaq.md) for unimportant information.
|
||||||
5. mmv: <https://github.com/rrthomas/mmv>
|
5. mmv: <https://github.com/rrthomas/mmv>
|
||||||
6. PathShortener: <https://github.com/ElectricRCAircraftGuy/eRCaGuy_PathShortener>
|
6. PathShortener: <https://github.com/ElectricRCAircraftGuy/eRCaGuy_PathShortener>
|
||||||
7. fuseblk-filename-fixer: <https://github.com/DDR0/fuseblk-filename-fixer>
|
7. fuseblk-filename-fixer: <https://github.com/DDR0/fuseblk-filename-fixer>
|
||||||
|
8. POSIX Portable Filename Set (see section 3.265):
|
||||||
|
<https://pubs.opengroup.org/onlinepubs/9799919799/>
|
||||||
|
|
|
||||||
118
main.go
118
main.go
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
fpath "path/filepath"
|
fpath "path/filepath"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -39,6 +40,16 @@ import (
|
||||||
*/
|
*/
|
||||||
type runeset [][2]rune
|
type runeset [][2]rune
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A basic search-and-replace operation. Replaces each instance of "Target"
|
||||||
|
* with each member of "Subs" in order, with all instances beyond
|
||||||
|
* length(Subs) being replaced by the last member of Subs.
|
||||||
|
*/
|
||||||
|
type replacement struct {
|
||||||
|
Target string
|
||||||
|
Subs []string
|
||||||
|
}
|
||||||
|
|
||||||
// Unrelated to the standard library's "context".
|
// Unrelated to the standard library's "context".
|
||||||
type context struct {
|
type context struct {
|
||||||
FileList []string
|
FileList []string
|
||||||
|
|
@ -48,6 +59,8 @@ type context struct {
|
||||||
DoHelp bool
|
DoHelp bool
|
||||||
DoVersion bool
|
DoVersion bool
|
||||||
Rset runeset
|
Rset runeset
|
||||||
|
TruncLen int
|
||||||
|
Replacements []replacement
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -76,14 +89,23 @@ var FAT_RUNESET = runeset{
|
||||||
}
|
}
|
||||||
|
|
||||||
var POSIX_PORTABLE_RUNESET = runeset{
|
var POSIX_PORTABLE_RUNESET = runeset{
|
||||||
{ 0x2d, 0x2d },
|
{ 0x2d, 0x2e },
|
||||||
{ 0x2e, 0x2e },
|
|
||||||
{ 0x5f, 0x5f },
|
{ 0x5f, 0x5f },
|
||||||
{ 0x30, 0x39 },
|
{ 0x30, 0x39 },
|
||||||
{ 0x41, 0x5a },
|
{ 0x41, 0x5a },
|
||||||
{ 0x61, 0x7a },
|
{ 0x61, 0x7a },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var SHELL_RUNESET = runeset{
|
||||||
|
{ 0x25, 0x25 },
|
||||||
|
{ 0x2d, 0x2e },
|
||||||
|
{ 0x30, 0x39 },
|
||||||
|
{ 0x41, 0x5a },
|
||||||
|
{ 0x5f, 0x5f },
|
||||||
|
{ 0x61, 0x7a },
|
||||||
|
{ 0xc0, MAX_CODE_POINT },
|
||||||
|
}
|
||||||
|
|
||||||
/// MAIN FUNCTIONS
|
/// MAIN FUNCTIONS
|
||||||
/**
|
/**
|
||||||
* Compares paths so that all files come before the directories that contain
|
* Compares paths so that all files come before the directories that contain
|
||||||
|
|
@ -189,6 +211,32 @@ func (ctx *context) restrictRuneset(s string) string {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Truncate to the number of bytes given by the context, but don't break in
|
||||||
|
* the middle of a rune.
|
||||||
|
*/
|
||||||
|
func (ctx *context) truncate(name string) string {
|
||||||
|
if ctx.TruncLen < 1 {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
for byteIndex, _ := range name {
|
||||||
|
if byteIndex > ctx.TruncLen {
|
||||||
|
return name[:byteIndex]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *context) searchAndReplace(name string) string {
|
||||||
|
for _, rep := range ctx.Replacements {
|
||||||
|
for i:=0; i<len(rep.Subs)-1; i++ {
|
||||||
|
name = strings.Replace(name, rep.Target, rep.Subs[i], 1)
|
||||||
|
}
|
||||||
|
name = strings.ReplaceAll(name, rep.Target, rep.Subs[len(rep.Subs)-1])
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
func warn(msg string) {
|
func warn(msg string) {
|
||||||
fmt.Fprintf(os.Stderr, "\x1b[33m%s\x1b[0m\n", msg)
|
fmt.Fprintf(os.Stderr, "\x1b[33m%s\x1b[0m\n", msg)
|
||||||
}
|
}
|
||||||
|
|
@ -211,6 +259,8 @@ func parseCLIArgs(args []string) (context, error) {
|
||||||
DoHelp: false,
|
DoHelp: false,
|
||||||
DoVersion: false,
|
DoVersion: false,
|
||||||
Rset: FAT_RUNESET,
|
Rset: FAT_RUNESET,
|
||||||
|
TruncLen: 0,
|
||||||
|
Replacements: []replacement{},
|
||||||
}
|
}
|
||||||
index := 1
|
index := 1
|
||||||
isFlag := func(arg string) bool {
|
isFlag := func(arg string) bool {
|
||||||
|
|
@ -228,6 +278,43 @@ func parseCLIArgs(args []string) (context, error) {
|
||||||
result.Strategy = args[index]
|
result.Strategy = args[index]
|
||||||
case "-p", "--portable":
|
case "-p", "--portable":
|
||||||
result.Rset = POSIX_PORTABLE_RUNESET
|
result.Rset = POSIX_PORTABLE_RUNESET
|
||||||
|
case "-e", "--valid-set":
|
||||||
|
index++
|
||||||
|
switch args[index] {
|
||||||
|
case "fat":
|
||||||
|
result.Rset = FAT_RUNESET
|
||||||
|
case "posix":
|
||||||
|
result.Rset = POSIX_PORTABLE_RUNESET
|
||||||
|
case "shell":
|
||||||
|
result.Rset = SHELL_RUNESET
|
||||||
|
default:
|
||||||
|
return result, errors.New(
|
||||||
|
"Invalid character set! Please choose one of fat,posix,shell",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case "-c", "--replace":
|
||||||
|
index++
|
||||||
|
target, subs, ok := strings.Cut(args[index], ":")
|
||||||
|
if !ok {
|
||||||
|
return result, errors.New(fmt.Sprintf(
|
||||||
|
"Incorrect syntax (<<%s>>) for replacement: please use '{TARGET}:{REPLACEMENT1},{REPLACEMENT2},...'",
|
||||||
|
args[index],
|
||||||
|
))
|
||||||
|
}
|
||||||
|
result.Replacements = append(
|
||||||
|
result.Replacements,
|
||||||
|
replacement{ target, strings.Split(subs, ",") },
|
||||||
|
)
|
||||||
|
case "-t", "--truncate":
|
||||||
|
index++
|
||||||
|
var err error
|
||||||
|
result.TruncLen, err = strconv.Atoi(args[index])
|
||||||
|
if err != nil || result.TruncLen < 1 {
|
||||||
|
return result, errors.New(fmt.Sprintf(
|
||||||
|
"Invalid truncation length: <<%s>>",
|
||||||
|
args[index],
|
||||||
|
))
|
||||||
|
}
|
||||||
case "-h", "--help":
|
case "-h", "--help":
|
||||||
result.DoHelp = true
|
result.DoHelp = true
|
||||||
case "-v", "--version":
|
case "-v", "--version":
|
||||||
|
|
@ -258,11 +345,28 @@ func printHelp() {
|
||||||
(?,\,*,etc.) are removed.
|
(?,\,*,etc.) are removed.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-s,--strategy
|
|
||||||
-h,--help
|
|
||||||
-v,--version
|
|
||||||
-r,--recurse DIRECTORY
|
-r,--recurse DIRECTORY
|
||||||
|
Recursively search for files in DIRECTORY with bad characters
|
||||||
|
-s,--strategy remove|represent
|
||||||
|
What to do with bad characters; either replace with a representation or
|
||||||
|
remove
|
||||||
|
-e,--valid-set fat|posix|shell
|
||||||
|
Select a set of characters to limit filenames to.
|
||||||
|
-p,--portable
|
||||||
|
Alias for "--valid-set posix"
|
||||||
|
-t,--truncate LENGTH
|
||||||
|
Truncate all filenames to at most LENGTH bytes
|
||||||
|
-c,--replace TARGET:REPLACEMENT1,REPLACEMENT2,...
|
||||||
|
Replace each instance of TARGET in file names with REPLACEMENT1 the
|
||||||
|
first time they appear, REPLACEMENT2 the second time, and so on
|
||||||
|
-n,--dry-run
|
||||||
|
Do not rename anything, just print what would be done
|
||||||
|
-h,--help
|
||||||
|
Show this message
|
||||||
|
-v,--version
|
||||||
|
Show version
|
||||||
|
|
||||||
|
See man page for more details
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -290,9 +394,9 @@ func main() {
|
||||||
// Calculate the new name
|
// Calculate the new name
|
||||||
oldName := fpath.Base(file)
|
oldName := fpath.Base(file)
|
||||||
dirName := fpath.Dir(file)
|
dirName := fpath.Dir(file)
|
||||||
newName := ctx.restrictRuneset(
|
newName := ctx.truncate(ctx.restrictRuneset(ctx.searchAndReplace(
|
||||||
strings.ToValidUTF8(oldName, "_INVALID_"),
|
strings.ToValidUTF8(oldName, "_INVALID_"),
|
||||||
)
|
)))
|
||||||
newPath := fpath.Join(dirName, newName)
|
newPath := fpath.Join(dirName, newName)
|
||||||
// Check that we want to rename this file
|
// Check that we want to rename this file
|
||||||
if newPath == file {
|
if newPath == file {
|
||||||
|
|
|
||||||
124
man.md
124
man.md
|
|
@ -1,4 +1,4 @@
|
||||||
# owl(1)
|
# OWL(1)
|
||||||
|
|
||||||
*The moon is bright. The owls are ready for the hunt.*
|
*The moon is bright. The owls are ready for the hunt.*
|
||||||
|
|
||||||
|
|
@ -6,7 +6,7 @@ Owl is a file renaming tool; it removes character from file names which are
|
||||||
not compatible with `FAT` file systems (typically `FAT32` or `exFAT`).
|
not compatible with `FAT` file systems (typically `FAT32` or `exFAT`).
|
||||||
|
|
||||||
```
|
```
|
||||||
owl COMMAND OPTIONS FILE1 FILE2 ...
|
owl OPTIONS FILE1 FILE2 ...
|
||||||
```
|
```
|
||||||
|
|
||||||
Rename `FILE`s given at the command line such that all `FAT`-incompatible
|
Rename `FILE`s given at the command line such that all `FAT`-incompatible
|
||||||
|
|
@ -15,7 +15,11 @@ characters are removed. By default it replaces invalid characters with
|
||||||
flag below.
|
flag below.
|
||||||
|
|
||||||
|
|
||||||
## Options:
|
## OPTIONS:
|
||||||
|
### -r, \-\-recurse DIRECTORY
|
||||||
|
Recursively search DIRECTORY for files/directories to rename. Search
|
||||||
|
includes DIRECTORY itself.
|
||||||
|
|
||||||
### -s, \-\-strategy STRATEGY
|
### -s, \-\-strategy STRATEGY
|
||||||
Change what happens to invalid characters. Choices are:
|
Change what happens to invalid characters. Choices are:
|
||||||
|
|
||||||
|
|
@ -24,13 +28,38 @@ Change what happens to invalid characters. Choices are:
|
||||||
- "represent": replace each character with "\_Unum_", where "num" is the
|
- "represent": replace each character with "\_Unum_", where "num" is the
|
||||||
Unicode Code Point of the character.
|
Unicode Code Point of the character.
|
||||||
|
|
||||||
### -r, \-\-recurse DIRECTORY
|
### -e,\-\-valid-set fat|posix|shell
|
||||||
Recursively search DIRECTORY for files/directories to rename. Search
|
Select which characters are considered "invalid" or "bad". Options are:
|
||||||
includes DIRECTORY itself.
|
- fat: Characters which are valid in file names on FAT file systems. This
|
||||||
|
is the default.
|
||||||
|
- posix: The POSIX Portable Filename Character Set `A-Za-z0-9.-_`
|
||||||
|
- shell: Characters that *should* not need to be quoted when used in a
|
||||||
|
shell, i.e. all characters apart from control characters, whitespace, and
|
||||||
|
most punctuation.
|
||||||
|
|
||||||
|
### -p,\-\-portable
|
||||||
|
An alias for "\-\-valid-set posix".
|
||||||
|
|
||||||
|
### -t,\-\-truncate LENGTH
|
||||||
|
Truncate all given file names to at most `LENGTH` bytes. Note that if you
|
||||||
|
have file names with larger Unicode code points (like CJK characters), you
|
||||||
|
may get unexpected results with this option, though it will not cut the file
|
||||||
|
name part way through a character.
|
||||||
|
|
||||||
|
### -c,\-\-replace TARGET:REPLACEMENT1,REPLACEMENT2,...
|
||||||
|
Remove every instance of the string `TARGET` in every file name and replace
|
||||||
|
the first instance with `REPLACEMENT1`, the second with `REPLACEMENT2`, and
|
||||||
|
so on. If there are N `REPLACEMENT`'s and more than N instances of `TARGET`
|
||||||
|
in a file name, all instances after the Nth are replaced by the last
|
||||||
|
replacement. Replacements can be empty. See EXAMPLES below for this one.
|
||||||
|
|
||||||
|
Remember to quote `TARGET:REPLACEMENT1,...` so that your shell doesn't
|
||||||
|
misinterpret the colon.
|
||||||
|
|
||||||
### -n, \-\-dry-run
|
### -n, \-\-dry-run
|
||||||
Causes Owl to just print a representation of what would be done without
|
Causes Owl to just print a representation of what would be done without
|
||||||
actually renaming any files.
|
actually renaming any files. Checks for name collisions with existing files
|
||||||
|
or other renaming operations are still done.
|
||||||
|
|
||||||
### -v, \-\-version
|
### -v, \-\-version
|
||||||
Show version information.
|
Show version information.
|
||||||
|
|
@ -39,7 +68,66 @@ Show version information.
|
||||||
Show a small help message.
|
Show a small help message.
|
||||||
|
|
||||||
|
|
||||||
## Edge Cases & Other Tidbits
|
## EXAMPLES
|
||||||
|
Suppose we have a file called
|
||||||
|
```
|
||||||
|
PodEp 37: Why are there so many spaces in this file name?
|
||||||
|
```
|
||||||
|
in the current working directory. We will refer to it as FILE Then:
|
||||||
|
|
||||||
|
- The command
|
||||||
|
```
|
||||||
|
owl --replace ' :,-,_' FILE
|
||||||
|
```
|
||||||
|
will rename FILE to
|
||||||
|
`PodEp37:-Why_are_there_so_many_spaces_in_this_file_name_U3F_`
|
||||||
|
- The command
|
||||||
|
```
|
||||||
|
owl --truncate 48 --replace ' :,-,_' FILE
|
||||||
|
```
|
||||||
|
will rename FILE to
|
||||||
|
`PodEp37:-Why_are_there_so_many_spaces_in_this_fi`
|
||||||
|
- The command
|
||||||
|
```
|
||||||
|
owl --portable --strategy remove --truncate 48 FILE
|
||||||
|
```
|
||||||
|
will rename FILE to
|
||||||
|
`PodEp37:Whyaretheresomanyspacesinthisfilename`
|
||||||
|
|
||||||
|
Now suppose we have files "IMPORTANT FILE?" and "Important File".
|
||||||
|
- If you run
|
||||||
|
```
|
||||||
|
owl --strategy remove "IMPORTANT FILE?"
|
||||||
|
```
|
||||||
|
owl will just return an error, saying that the new name would collide with
|
||||||
|
"important file".
|
||||||
|
- If you run
|
||||||
|
```
|
||||||
|
owl --strategy remove --valid-set posix "IMPORTANT FILE?" "Important File"
|
||||||
|
```
|
||||||
|
then owl will will rename one file, but refuse to rename the other, as it
|
||||||
|
would conflict with the now-renamed first file.
|
||||||
|
Note you can use the `--dry-run` flag to check for collisions like this
|
||||||
|
before renaming anything.
|
||||||
|
|
||||||
|
|
||||||
|
## EDGE CASES & OTHER TIDBITS
|
||||||
|
### What order does owl truncate, remove characters, etc.?
|
||||||
|
Owl does things in the following order:
|
||||||
|
|
||||||
|
1. Remove invalid UTF-8
|
||||||
|
2. Replace stuff (see `--replace` flag)
|
||||||
|
3. Deal with invalid characters (see `--strategy` flag)
|
||||||
|
4. Truncate file name
|
||||||
|
|
||||||
|
The order in which you use the flags does not change this.
|
||||||
|
|
||||||
|
### What if the new name Owl chooses for a file already exists?
|
||||||
|
Owl checks before each renaming operation to see if the new name already
|
||||||
|
exists, and skips that file if it does. The check is also case-insensitive.
|
||||||
|
See [EXAMPLES](#examples) for more details.
|
||||||
|
|
||||||
|
|
||||||
### What doesn't FAT allow?
|
### What doesn't FAT allow?
|
||||||
Mainly `*<>\|/:?'`
|
Mainly `*<>\|/:?'`
|
||||||
|
|
||||||
|
|
@ -68,6 +156,26 @@ Owl has only been tested so far on Linux with a UTF-8 locale. UTF-16 support
|
||||||
on Windows is likely possible, but has not been tested. Please contact me if
|
on Windows is likely possible, but has not been tested. Please contact me if
|
||||||
you would like Windows support (see below).
|
you would like Windows support (see below).
|
||||||
|
|
||||||
|
### What about other restrictions on file names?
|
||||||
|
Windows (and by extension FAT file systems, which come from Microsoft) may
|
||||||
|
have other restrictions on file names, like specific disallowed names like
|
||||||
|
"CON" or names which ends with a ".". Owl does not check for such things;
|
||||||
|
you should look for those using a tool like `find`.
|
||||||
|
|
||||||
|
For completeness, the disallowed names are
|
||||||
|
```
|
||||||
|
CON,AUX,COM1,COM2COM3,COM4,LPT1,LPT2,LPT3,PRN,NUL
|
||||||
|
```
|
||||||
|
|
||||||
|
### What about symbolic links (a.k.a. symlinks)?
|
||||||
|
Owl will rename the *link*, but not the file that the link points to, even
|
||||||
|
if the link is broken. If you are having issues with broken symbolic links
|
||||||
|
and are on a POSIX/UNIX-like system, you can use
|
||||||
|
```
|
||||||
|
find -L DIRECTORY -type l
|
||||||
|
```
|
||||||
|
to find all broken links in DIRECTORY.
|
||||||
|
|
||||||
|
|
||||||
## AUTHORS & COPYRIGHT
|
## AUTHORS & COPYRIGHT
|
||||||
Written by user SixteenThousand of github.com (email
|
Written by user SixteenThousand of github.com (email
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,10 @@ owltest_setup() {
|
||||||
$'No\u0338rse\u0301' \
|
$'No\u0338rse\u0301' \
|
||||||
'Question? Why' \
|
'Question? Why' \
|
||||||
'QUESTION: WHY'
|
'QUESTION: WHY'
|
||||||
|
ln -s "${PWD}/::>?" ascii_dir/file
|
||||||
|
ln -s "${PWD}/::>?" ascii_dir/fi?le
|
||||||
|
ln -s "${PWD}/no-change-needed" 'ascii_dir/oh?'
|
||||||
|
ln -s /invalid/path 'ascii_dir/bad link'
|
||||||
}
|
}
|
||||||
|
|
||||||
owltest_teardown() {
|
owltest_teardown() {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue