Golang file renaming - go

Since yesterday I was trying to write a script/program that would change filenames. Alongside learning golang I also started to make animations and for some reason Illustrator names every .png file I make like this - "name_working space 1 copy.png" and "* copy (number).png". I was tired of renaming those files manually and today I found this code and modified 1 line of it to get rid of "_working space 1 copy" but only from those files that have numbers. That leaves me with 2 files that are first frames of my animations and those are - "name_working space 1.png"; "name_working space 1 copy.png". I could totaly live with it by leaving those 2 blank but since I try to learn golang I wanted to ask if I can improve it to replace every single filename. I started learning monday this week.
import (
"fmt"
"log"
"os"
"path/filepath"
"regexp"
)
func currentDir() {
dir := "D:\\GoLang\\src\\gocourse\\renamefile\\rename"
file, err := os.Open(dir)
if err != nil {
log.Fatalf("failed using this directory %s", err)
}
defer file.Close()
list, err := file.Readdirnames(0)
if err != nil {
log.Fatalf("failed reading directory: %s", err)
}
re := regexp.MustCompile("_working space 1 copy ")
for _, name := range list {
oldName := name
fmt.Println("Old name - ", oldName)
newName := re.ReplaceAllString(oldName, "$1")
fmt.Println("New Name - ", newName)
err := os.Rename(filepath.Join(dir, oldName), filepath.Join(dir, newName))
if err != nil {
log.Printf("error renaming file: %s", err)
continue
}
fmt.Println("File names have been changed")
}
}
func main() {
currentDir()
}
ball_working space 1.png --> ball-1.png;
ball_working space 1 copy.png --> ball-2.png;
ball_working space 1 copy 1.png --> ball-3.png;
ball_working space 1 copy 2.png --> ball-4.png etc.

Here is a reimplementation of your code. However it is not complete, as you need
to better clarify what the names look like before, and what they should look like
after.
package main
import (
"os"
"path/filepath"
"strings"
)
func main() {
dir := `D:\GoLang\src\gocourse\renamefile\rename`
list, err := os.ReadDir(dir)
if err != nil {
panic(err)
}
for _, each := range list {
name := each.Name()
newName := strings.ReplaceAll(name, "_working space 1 copy", "")
os.Rename(filepath.Join(dir, name), filepath.Join(dir, newName))
}
}

I guess you could just parse the first part of each file name until it encounters an underscore. A function to illustrate if you are sure that the file you want to extract always ends with the same symbol '_'.
fileNames := []string{"ball_working space 1.png", "ball_working space 1 copy.png",
"ball_working space 1 copy 1.png", "ball_working space 1 copy 2.png"}
func parseName(names []string) {
res := []string{""}
for ind, val := range names {
data := val[:strings.IndexByte(val, '_')] // reads until the hardcoded symbol
data += "-" + strconv.Itoa(ind)
res = append(res, data)
return res
}
Then you will have a slice with all the "correct" file names and you can unpack it in your way of preference.

Related

Capturing the output of exec.Command("find", "./helloworld/workspace", "-name", "*.java").Output()

I'm trying to find the path of the helloworld.java file so that I can pass it down to a compiler function.
What I have:
I'm expecting this to return the path, of type []byte and then stringify it, of the only helloworld.java file in this directory and then pass it down to Java() function.
filePath, _ := exec.Command("find", "./helloworld/workspace", "-name", "*.java").Output()
Java(string(filePath))
The Problem is that cmd := exec.Command("javac", filePath) in my java() function is not recognizing the file path therefore not compiling it.
But if I hardcode the path that I get from exec.Command("find) like this:
This works fine
cmd := exec.Command("javac", "./helloworld/workspace/src/main/java/com/coveros/demo/helloworld/HelloWorld.java")
err := cmd.Run()
But this does not work:
What am I missing, How do I fix this?
func Java(filePath string) {
fmt.Println("compiler start")
cmd := exec.Command("javac", filePath)
err := cmd.Run()
if err != nil {
log.Fatal(err)
}
fmt.Println("compiler End")
}
I think the result from find is returning multiple possible paths which are separated by a newline "\n". The newline will be "hidden" if it is printed to the command line. You can try this fmt.Println(stringPath + "hello, am I on a new line?") to show the stringPath has a new line in it.
See the below which uses a similar version of find looking for json files, then splits the string by newlines and then loops through these paths. If the path is blank (which it can be) it skips over it.
package main
import (
"fmt"
"os/exec"
"strings"
)
func main() {
filePath, err := exec.Command("find", ".", "-name", "*.json").Output()
if err != nil {
panic(err)
}
stringPath := string(filePath)
paths := strings.Split(stringPath, "\n")
CatFile(paths)
}
func CatFile(filePaths []string) {
for _, path := range filePaths {
if len(path) == 0 {
continue
}
output, err := exec.Command("cat", path).Output()
if err != nil {
fmt.Println("Error!")
fmt.Println(err)
}
fmt.Println(string(output))
}
}
See this related question, which discusses this: Go lang differentiate "\n" and line break

List FTP files with goftp

I am trying to write a simple Go program which connects to an FTP server, list the files in a specified directory and pulls them.
The code is this:
package main
import (
"bytes"
"fmt"
"github.com/secsy/goftp"
"io/ioutil"
"log"
"os"
"path"
"time"
)
func main() {
config := goftp.Config{
User: "anonymous",
Password: "root#local.me",
ConnectionsPerHost: 21,
Timeout: 10 * time.Second,
Logger: os.Stderr,
}
// Connecting to the server
client, dailErr := goftp.DialConfig(config, "ftp.example.com")
if dailErr != nil {
log.Fatal(dailErr)
panic(dailErr)
}
// setting the search directory
dir := "/downloads/"
files, err := client.ReadDir(dir)
if err != nil {
for _, file := range files {
if file.IsDir() {
path.Join(dir, file.Name())
} else {
fmt.Println("the file is %s", file.Name())
}
}
}
// this section works , I am setting a file name and I can pull it
// if I mark the search part
ret_file := "example.PDF"
fmt.Println("Retrieving file: ", ret_file)
buf := new(bytes.Buffer)
fullPathFile := dir + ret_file
rferr := client.Retrieve(fullPathFile, buf)
if rferr != nil {
panic(rferr)
}
fmt.Println("writing data to file", ret_file)
fmt.Println("Opening file", ret_file, "for writing")
w, _ := ioutil.ReadAll(buf)
ferr := ioutil.WriteFile(ret_file, w, 0644)
if ferr != nil {
log.Fatal(ferr)
panic(ferr)
} else {
fmt.Println("Writing", ret_file, " completed")
}
}
For some reason I am getting an error on the ReadDir function.
I need to grab the files names so I can download them.
You're attempting to loop through files when ReadDir() returns an error. That will never work, as any time an error is returned files is nil.
This is pretty standard behavior and can be confirmed by reading the implementation of ReadDir().
I'm guessing you may have used the the example from the project used to demonstrate ReadDir() as a starting point. Within the example, the error handling is involved because it's deciding whether or not to continue walking the directory tree. However, note that when ReadDir() returns an error that doesn't result in stopping the program, the subsequent for loop is a no-op, since files is nil.
Here's a small program that demonstrates successfully using the results of Readdir() in a straightforward manner:
package main
import (
"fmt"
"github.com/secsy/goftp"
)
const (
ftpServerURL = "ftp.us.debian.org"
ftpServerPath = "/debian/"
)
func main() {
client, err := goftp.Dial(ftpServerURL)
if err != nil {
panic(err)
}
files, err := client.ReadDir(ftpServerPath)
if err != nil {
panic(err)
}
for _, file := range files {
fmt.Println(file.Name())
}
}
It outputs (which matches the current listing at http://ftp.us.debian.org/debian/):
$ go run goftp-test.go
README
README.CD-manufacture
README.html
README.mirrors.html
README.mirrors.txt
dists
doc
extrafiles
indices
ls-lR.gz
pool
project
tools
zzz-dists

Go regExRepl-Script does not change the text file

my go script should add one newline before matching the regEx-Search-String ^(.+[,]+\n).
The Prototype i had tested before into the editor:
i want add newlines before this lines: \n$1.
This works if i try it into the Text-Editor.
If i try this (see line 24) with my script it is changing nothing and sends no error.
Any ideas what i do wrong?
Example
i like to use PCRE like it works in this Example https://regex101.com/r/sB9wW6/17
Same Example here:
Example source
Dear sir,
Thanks for your interest.
expected result
#### here is a newline ####
Dear sir,
Thanks for your interest.
result is (produced by the script below)
Dear sir,
Thanks for your interest.
go script:
// replace in files and store the new copy of it.
package main
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"time"
)
func visit(path string, fi os.FileInfo, err error) error {
matched, err := filepath.Match("*.csv", fi.Name())
if err != nil {
panic(err)
return err
}
if matched {
read, err := ioutil.ReadFile(path)
if err != nil {
panic(err)
}
newContents := string(read)
newContents = regExRepl(`^(.+[,]+\n)`, newContents, `\n\n\n$1`)
var re = regexp.MustCompile(`[\W]+`)
t_yymmdd := regexp.MustCompile(`[\W]+`).ReplaceAllString(time.Now().Format(time.RFC3339), `-`)[:10]
t_hhss := re.ReplaceAllString(time.Now().Format(time.RFC3339), `-`)[11:19]
t_yymmddhhss := t_yymmdd + "_" + t_hhss
fmt.Println(t_yymmddhhss)
filePath := fileNameWithoutExtension(path) + t_yymmddhhss + ".csv"
err = ioutil.WriteFile(filePath, []byte(newContents), 0)
if err != nil {
panic(err)
}
}
return nil
}
func regExRepl(regExPatt string, newContents string, regExRepl string) string {
return regexp.MustCompile(regExPatt).ReplaceAllString(newContents, regExRepl)
}
func main() {
err := filepath.Walk("./november2020messages.csv", visit) // <== read all files in current folder 20:12:06 22:44:42
if err != nil {
panic(err)
}
}
func fileNameWithoutExtension(fileName string) string {
return strings.TrimSuffix(fileName, filepath.Ext(fileName))
}
for interpretation \n as newline don't us
`\n`` use "\n"
may use ^(.+[,]+) instead ^(.+[,]+\n) and ad (?m) before for multi-line replacements
this suggestion you could test here: https://play.golang.org/p/25_0GJ93oCT
The following example illustrates the difference (in golang-playground here https://play.golang.org/p/FkPwElhx-Xu ):
// example from:
package main
import (
"fmt"
"regexp"
)
func main() {
newContents := `line 1,
line 2
line a,
line b`
newContents1 := regexp.MustCompile(`^(.+[,]+\n)`).ReplaceAllString(newContents, `\n$1`)
fmt.Println("hi\n" + newContents1)
newContents1 = regexp.MustCompile(`(?m)^(.+[,]+\n)`).ReplaceAllString(newContents, "\n$1")
fmt.Println("ho\n" + newContents1)
}
Result:
hi
\nline 1,
line 2
line a,
line b
ho
line 1,
line 2
line a,
line b

Replacing a line within a file with Golang

I'm new to Golang, starting out with some examples. Currently, what I'm trying to do is reading a file line by line and replace it with another string in case it meets a certain condition.
The file is use for testing purposes contains four lines:
one
two
three
four
The code working on that file looks like this:
func main() {
file, err := os.OpenFile("test.txt", os.O_RDWR, 0666)
if err != nil {
panic(err)
}
reader := bufio.NewReader(file)
for {
fmt.Print("Try to read ...\n")
pos,_ := file.Seek(0, 1)
log.Printf("Position in file is: %d", pos)
bytes, _, _ := reader.ReadLine()
if (len(bytes) == 0) {
break
}
lineString := string(bytes)
if(lineString == "two") {
file.Seek(int64(-(len(lineString))), 1)
file.WriteString("This is a test.")
}
fmt.Printf(lineString + "\n")
}
file.Close()
}
As you can see in the code snippet, I want to replace the string "two" with "This is a test" as soon as this string is read from the file.
In order to get the current position within the file I use Go's Seek method.
However, what happens is that always the last line gets replaced by This is a test, making the file looking like this:
one
two
three
This is a test
Examining the output of the print statement which writes the current file position to the terminal, I get that kind of output after the first line has been read:
2016/12/28 21:10:31 Try to read ...
2016/12/28 21:10:31 Position in file is: 19
So after the first read, the position cursor already points to the end of my file, which explains why the new string gets appended to the end. Does anyone understand what is happening here or rather what is causing that behavior?
The Reader is not controller by the file.Seek. You have declared the reader as: reader := bufio.NewReader(file) and then you read one line at a time bytes, _, _ := reader.ReadLine() however the file.Seek does not change the position that the reader is reading.
Suggest you read about the ReadSeeker in the docs and switch over to using that. Also there is an example using the SectionReader.
Aside from the incorrect seek usage, the difficulty is that the line you're replacing isn't the same length as the replacement. The standard approach is to create a new (temporary) file with the modifications. Assuming that is successful, replace the original file with the new one.
package main
import (
"bufio"
"io"
"io/ioutil"
"log"
"os"
)
func main() {
// file we're modifying
name := "text.txt"
// open original file
f, err := os.Open(name)
if err != nil {
log.Fatal(err)
}
defer f.Close()
// create temp file
tmp, err := ioutil.TempFile("", "replace-*")
if err != nil {
log.Fatal(err)
}
defer tmp.Close()
// replace while copying from f to tmp
if err := replace(f, tmp); err != nil {
log.Fatal(err)
}
// make sure the tmp file was successfully written to
if err := tmp.Close(); err != nil {
log.Fatal(err)
}
// close the file we're reading from
if err := f.Close(); err != nil {
log.Fatal(err)
}
// overwrite the original file with the temp file
if err := os.Rename(tmp.Name(), name); err != nil {
log.Fatal(err)
}
}
func replace(r io.Reader, w io.Writer) error {
// use scanner to read line by line
sc := bufio.NewScanner(r)
for sc.Scan() {
line := sc.Text()
if line == "two" {
line = "This is a test."
}
if _, err := io.WriteString(w, line+"\n"); err != nil {
return err
}
}
return sc.Err()
}
For more complex replacements, I've implemented a package which can replace regular expression matches. https://github.com/icholy/replace
import (
"io"
"regexp"
"github.com/icholy/replace"
"golang.org/x/text/transform"
)
func replace2(r io.Reader, w io.Writer) error {
// compile multi-line regular expression
re := regexp.MustCompile(`(?m)^two$`)
// create replace transformer
tr := replace.RegexpString(re, "This is a test.")
// copy while transforming
_, err := io.Copy(w, transform.NewReader(r, tr))
return err
}
OS package has Expand function which I believe can be used to solve similar problem.
Explanation:
file.txt
one
two
${num}
four
main.go
package main
import (
"fmt"
"os"
)
var FILENAME = "file.txt"
func main() {
file, err := os.ReadFile(FILENAME)
if err != nil {
panic(err)
}
mapper := func(placeholderName string) string {
switch placeholderName {
case "num":
return "three"
}
return ""
}
fmt.Println(os.Expand(string(file), mapper))
}
output
one
two
three
four
Additionally, you may create a config (yml or json) and
populate that data in the map that can be used as a lookup table to store placeholders as well as their replacement strings and modify mapper part to use this table to lookup placeholders from input file.
e.g map will look like this,
table := map[string]string {
"num": "three"
}
mapper := func(placeholderName string) string {
if val, ok := table[placeholderName]; ok {
return val
}
return ""
}
References:
os.Expand documentation: https://pkg.go.dev/os#Expand
Playground

How to read/scan a .txt file in GO

The .txt file has many lines which each contain a single word. So I open the file and pass it to the reader:
file, err := os.Open("file.txt")
check(err)
reader := bufio.NewReader(file)
Now I want to store each line in a slice of strings. I believe I need to use ReadBytes, ReadString, ReadLine, or on of the Scan functions. Any advice on how to implement this would be appreciated. Thanks.
You can use ioutil.ReadFile() to read all lines into a byte slice and then call split on the result:
package main
import (
"fmt"
"io/ioutil"
"log"
"strings"
)
func main() {
data, err := ioutil.ReadFile("/etc/passwd")
if err != nil {
log.Fatal(err)
}
lines := strings.Split(string(data), "\n")
for _, line := range lines {
fmt.Println("line:", string(line))
}
}
Having r as an instance of *bufio.Reader, and myList as a slice of strings, than one could just loop and read lines till EOL.
for {
line, err := r.ReadBytes('\n')
if err != nil {
break
}
myList = append(myList, string(line))
}

Resources