How to get the state of the keyboard in AHK script? - keyboard

In AHK, how to get the array of boolean values representing the state of every key on the keyboard?

Different keyboard layouts come with different keys. You have to know the names of the keys in order to check them and it has to be done key by key.
You would basically use GetKeyState("KeyName","P").
If you have an array of keys that you want to check you could loop ofer the key array, cehck every key and store the results in another array:
keysToCheck = ["q","w","e","r","t","y","u","i","o","p"]
keyStates = {}
for i, key in keysToCheck
{
keyState := GetKeyState("KeyName","P")
keyStates.Insert(key,keyState)
}
; keyStates now contains all the key states
MsgBox % keyStates["q"]
MsgBox % keyStates["w"]
(untested)

I did find an amazing AHK Script that can Monitor All your Pressed keys and keyboard shortcuts combinations and Mouse clicks, The Script is long but then you are able to get the state of All the keyboard movements.
if you look to the Codeline GuiControl, , HotkeyText, %HotkeyStr% from the KeypressOSD.ahk script - then this %HotkeyStr% is the variable where you recieve the Value what you did pressed.
If you Change the Code a litte bit - and remove some Gui's codelines lines + write this %HotkeyStr% value
to the Windows Registry, you can then get everytime the key press value from the registry with one codeline and use it, then in any kind of ahk scripts.
You can Replace this and remove some GUI codelines.
RegWrite, REG_SZ, HKEY_CURRENT_USER,software\GetKeypressValue,KeypressValue,%HotkeyStr%
Now you can use it in any ahk script with one codeline. (note - run it together with KeypressOSD.ahk script)
RegRead, KeypressValue, HKEY_CURRENT_USER,software\GetKeypressValue,KeypressValue ; read KeypressValue
If you Look to this Youtube Video you can see what it can do.
Detect Keypress Value From Windows Registry key.
note!! This program was created by Author RaptorX.
And It works on Windows 10 System.
KeypressOSD.ahk
; KeypressOSD.ahk
; Open this Script in Wordpad and For Changelog look to the Bottom of the script.
;This code works with a getkeyname from a Dllcall (See Bottom Script- by Lexikos)
;you can press the esc key to exit.
#SingleInstance force
#NoEnv
SetBatchLines, -1
ListLines, Off
; Settings
global TransN := 200 ; 0~255
global ShowSingleKey := True
global ShowMouseButton := True
global ShowSingleModifierKey := True
global ShowModifierKeyCount := true
global ShowStickyModKeyCount := false
global DisplayTime := 2000 ; In milliseconds
global GuiPosition := "Bottom" ; Top or Bottom
global FontSize := 50
global GuiHeight := 115
CreateGUI()
CreateHotkey()
return
OnKeyPressed:
try {
key := GetKeyStr()
ShowHotkey(key)
SetTimer, HideGUI, % -1 * DisplayTime
}
return
OnKeyUp:
return
_OnKeyUp:
tickcount_start := A_TickCount
return
CreateGUI() {
global
Gui, +AlwaysOnTop -Caption +Owner +LastFound +E0x20
Gui, Margin, 0, 0
Gui, Color, Black
Gui, Font, cWhite s%FontSize% bold, Arial
Gui, Add, Text, vHotkeyText Center y20
WinSet, Transparent, %TransN%
}
CreateHotkey() {
Loop, 95
{
k := Chr(A_Index + 31)
k := (k = " ") ? "Space" : k
Hotkey, % "~*" k, OnKeyPressed
Hotkey, % "~*" k " Up", _OnKeyUp
}
Loop, 24 ; F1-F24
{
Hotkey, % "~*F" A_Index, OnKeyPressed
Hotkey, % "~*F" A_Index " Up", _OnKeyUp
}
Loop, 10 ; Numpad0 - Numpad9
{
Hotkey, % "~*Numpad" A_Index - 1, OnKeyPressed
Hotkey, % "~*Numpad" A_Index - 1 " Up", _OnKeyUp
}
Otherkeys := "WheelDown|WheelUp|WheelLeft|WheelRight|XButton1|XButton2|Browser_Forward|Browser_Back|Browser_Refresh|Browser_Stop|Browser_Search|Browser_Favorites|Browser_Home|Volume_Mute|Volume_Down|Volume_Up|Media_Next|Media_Prev|Media_Stop|Media_Play_Pause|Launch_Mail|Launch_Media|Launch_App1|Launch_App2|Help|Sleep|PrintScreen|CtrlBreak|Break|AppsKey|NumpadDot|NumpadDiv|NumpadMult|NumpadAdd|NumpadSub|NumpadEnter|Tab|Enter|Esc|BackSpace"
. "|Del|Insert|Home|End|PgUp|PgDn|Up|Down|Left|Right|ScrollLock|CapsLock|NumLock|Pause|sc145|sc146|sc046|sc123"
Loop, parse, Otherkeys, |
{
Hotkey, % "~*" A_LoopField, OnKeyPressed
Hotkey, % "~*" A_LoopField " Up", _OnKeyUp
}
If ShowMouseButton {
Loop, Parse, % "LButton|MButton|RButton", |
Hotkey, % "~*" A_LoopField, OnKeyPressed
}
for i, mod in ["Ctrl", "Shift", "Alt"] {
Hotkey, % "~*" mod, OnKeyPressed
Hotkey, % "~*" mod " Up", OnKeyUp
}
for i, mod in ["LWin", "RWin"]
Hotkey, % "~*" mod, OnKeyPressed
}
ShowHotkey(HotkeyStr) {
WinGetPos, ActWin_X, ActWin_Y, ActWin_W, ActWin_H, A
if !ActWin_W
throw
text_w := (ActWin_W > A_ScreenWidth) ? A_ScreenWidth : ActWin_W
GuiControl, , HotkeyText, %HotkeyStr%
GuiControl, Move, HotkeyText, w%text_w% Center
if (GuiPosition = "Top")
gui_y := ActWin_Y
else
gui_y := (ActWin_Y+ActWin_H) - 115 - 50
Gui, Show, NoActivate x%ActWin_X% y%gui_y% h%GuiHeight% w%text_w%
}
GetKeyStr() {
static modifiers := ["Ctrl", "Shift", "Alt", "LWin", "RWin"]
static repeatCount := 1
for i, mod in modifiers {
if GetKeyState(mod)
prefix .= mod " + "
}
if (!prefix && !ShowSingleKey)
throw
key := SubStr(A_ThisHotkey, 3)
if (key ~= "i)^(Ctrl|Shift|Alt|LWin|RWin)$") {
if !ShowSingleModifierKey {
throw
}
key := ""
prefix := RTrim(prefix, "+ ")
if ShowModifierKeyCount {
if !InStr(prefix, "+") && IsDoubleClickEx() {
if (A_ThisHotKey != A_PriorHotKey) || ShowStickyModKeyCount {
if (++repeatCount > 1) {
prefix .= " ( * " repeatCount " )"
}
} else {
repeatCount := 0
}
} else {
repeatCount := 1
}
}
} else {
if ( StrLen(key) = 1 ) {
key := GetKeyChar(key, "A")
} else if ( SubStr(key, 1, 2) = "sc" ) {
key := SpecialSC(key)
} else if (key = "LButton") && IsDoubleClick() {
key := "Double-Click"
}
_key := (key = "Double-Click") ? "LButton" : key
static pre_prefix, pre_key, keyCount := 1
global tickcount_start
if (prefix && pre_prefix) && (A_TickCount-tickcount_start < 300) {
if (prefix != pre_prefix) {
result := pre_prefix pre_key ", " prefix key
} else {
keyCount := (key=pre_key) ? (keyCount+1) : 1
key := (keyCount>2) ? (key " (" keyCount ")") : (pre_key ", " key)
}
} else {
keyCount := 1
}
pre_prefix := prefix
pre_key := _key
repeatCount := 1
}
return result ? result : prefix . key
}
SpecialSC(sc) {
static k := {sc046: "ScrollLock", sc145: "NumLock", sc146: "Pause", sc123: "Genius LuxeMate Scroll"}
return k[sc]
}
; by Lexikos - https://autohotkey.com/board/topic/110808-getkeyname-for-other-languages/#entry682236
GetKeyChar(Key, WinTitle:=0) {
thread := WinTitle=0 ? 0
: DllCall("GetWindowThreadProcessId", "ptr", WinExist(WinTitle), "ptr", 0)
hkl := DllCall("GetKeyboardLayout", "uint", thread, "ptr")
vk := GetKeyVK(Key), sc := GetKeySC(Key)
VarSetCapacity(state, 256, 0)
VarSetCapacity(char, 4, 0)
n := DllCall("ToUnicodeEx", "uint", vk, "uint", sc
, "ptr", &state, "ptr", &char, "int", 2, "uint", 0, "ptr", hkl)
return StrGet(&char, n, "utf-16")
}
IsDoubleClick(MSec = 300) {
Return (A_ThisHotKey = A_PriorHotKey) && (A_TimeSincePriorHotkey < MSec)
}
IsDoubleClickEx(MSec = 300) {
preHotkey := RegExReplace(A_PriorHotkey, "i) Up$")
Return (A_ThisHotKey = preHotkey) && (A_TimeSincePriorHotkey < MSec)
}
HideGUI() {
Gui, Hide
}
esc::exitapp
;---------------------------------------------
; ChangeLog : v2.22 (2017-02-25) - Now pressing the same combination keys continuously more than 2 times,
; for example press Ctrl+V 3 times, will displayed as "Ctrl + v (3)"
; v2.21 (2017-02-24) - Fixed LWin/RWin not poping up start menu
; v2.20 (2017-02-24) - Added displaying continuous-pressed combination keys.
; e.g.: With CTRL key held down, pressing K and U continuously will shown as "Ctrl + k, u"
; v2.10 (2017-01-22) - Added ShowStickyModKeyCount option
; v2.09 (2017-01-22) - Added ShowModifierKeyCount option
; v2.08 (2017-01-19) - Fixed a bug
; v2.07 (2017-01-19) - Added ShowSingleModifierKey option (default is True)
; v2.06 (2016-11-23) - Added more keys. Thanks to SashaChernykh.
; v2.05 (2016-10-01) - Fixed not detecting "Ctrl + ScrollLock/NumLock/Pause". Thanks to lexikos.
; v2.04 (2016-10-01) - Added NumpadDot and AppsKey
; v2.03 (2016-09-17) - Added displaying "Double-Click" of the left mouse button.
; v2.02 (2016-09-16) - Added displaying mouse button, and 3 settings (ShowMouseButton, FontSize, GuiHeight)
; v2.01 (2016-09-11) - Display non english keyboard layout characters when combine with modifer keys.
; v2.00 (2016-09-01) - Removed the "Fade out" effect because of its buggy.
; - Added support for non english keyboard layout.
; - Added GuiPosition setting.
; v1.00 (2013-10-11) - First release.
;--------------------------------------------

Related

Adding Map Elements to Slice in Sequential Orde

I'm working on a text wrapping function. I want it to break a long line of text into string slices of a maximum length of characters. I've got it mostly working. However, sometimes the words are placed out of order.
This happens when there is a long word followed by a short word. I believe the program sees the longer word will not fit on the line so it skips that word and adds in the next word that will fit.
As this is text, the words must stay in the correct order. How can I force the loop to only add words in the correct order?
Actual Output:
[]string{" Go back out of the hotel entrance and your is", " room on lower ground a private street", " entrance."}
Expected Output:
[]string{" Go back out of the hotel entrance and your", " room is on lower ground a private street", " entrance."}
This is what I have so far.
Link: https://play.golang.org/p/YsCWoM9hQJV
package main
import (
"fmt"
"strings"
)
func main() {
directions := "Go back out of the hotel entrance and your room is on the lower ground a private street entrance."
ws := strings.Split(directions, " ")
neededSlices := strings.Count(directions, "") / 48
if strings.Count(directions, "")%48 != 0 {
neededSlices++
}
ls := make([]string, neededSlices, neededSlices)
keys := make(map[string]bool)
for i := 0; i < len(ls); i++ {
for _, v := range ws {
if _, ok := keys[v]; !ok {
if strings.Count(ls[i], "")+strings.Count(v, "") <= 48 {
ls[i] = ls[i] + " " + v
keys[v] = true
}
}
}
}
fmt.Printf("%#v", ls)
}
I think this is simple implementation of what you need
package main
import (
"fmt"
"strings"
)
func main() {
directions := "Go back out of the hotel entrance and your room is on the lower ground a private street entrance."
ws := strings.Split(directions, " ")
sl := []string{""}
i := 0
for _,word := range ws {
if (len(sl[i]) + len(word) + 1) >=48 {
i++
sl = append(sl, "")
}
sl[i] += " " + word
}
fmt.Printf("%#v", sl)
}
Link: https://play.golang.org/p/7R2TS6lv4Tm
The first problem I notice is your usage of a map. A map can only contain a key once. Due to this, your code will only contain each word once in one of the output slices.
The second problem is that you iterate over the whole ws array again for each iteration of the ls slice. I guess you tried to work around this issue with the map?
The solution would be to iterate only once over ws and assign the words to the index in ls.
Also note that strings.Count returns the number of characters (unicode points) in the string plus 1.
Your code btw also adds a space at the beginning of each string in the slice. I am not sure if this is intended (your expected output matches this). In my example solution I deviate from that so my output does not 100% match your stated expected output but I think it gives a more expected result.
package main
import (
"fmt"
"strings"
)
func main() {
directions := "Go back out of the hotel entrance and your hotel room is on the lower ground a private street entrance."
ws := strings.Split(directions, " ")
ls := []string{}
l := 0 // current line length
i := -1
for _, v := range ws {
lv := strings.Count(v, "")-1
if l == 0 || l+lv+1 > 48 {
i++
ls = append(ls, v)
l = lv
continue
}
ls[i] += " " + v
l += lv+1
}
fmt.Printf("%#v", ls)
}
Go Playground: https://play.golang.org/p/HhdX8RudiXn

ADA - Records in a hashmap. Problems with printing the hash map

I'm new to programming and this is my first attempt at a data container with records. I'm having difficulty with printing the hashmap between line 65-70. I'm guessing I need to break down the record and print each of its attributes individually but I'm not sure on the best way to do that. The error state 'no candidates match the actuals: missing argument for parameter.
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Text_IO.Unbounded_IO; use Ada.Text_IO.Unbounded_IO;
with Ada.Strings.Hash;
with Ada.Containers.Hashed_Maps;
with Ada.Characters.Handling;
procedure Hashmap_Records is
type Guest is record
Name : Unbounded_String := Null_Unbounded_String;
Attending : Boolean := False;
Vegitarian : Boolean := False;
Kids : Natural := 0;
end record;
function Equivalent_Keys(Left : Unbounded_String;
Right : Unbounded_String)
return Boolean is
begin
return Left = Right;
end Equivalent_Keys;
function U_To_L(Key : in Unbounded_String)
return Unbounded_String is
begin
return To_Unbounded_String(Ada.Characters.Handling.To_Lower(To_String(Key)));
end U_To_L;
function Hash_Func(Key : in Unbounded_String)
return Ada.Containers.Hash_Type is
begin
return Ada.Strings.Hash(To_String(Key));
end Hash_Func;
package Guest_Tracker is new Ada.Containers.Hashed_Maps(Key_Type => Unbounded_String,
Element_Type => Guest,
Hash => Hash_Func,
Equivalent_Keys => Equivalent_Keys);
Tracked_Guests : Guest_Tracker.Map;
User_Input : Natural := 0;
Name_Input : Unbounded_String := Null_Unbounded_String;
Name : Unbounded_String := Null_Unbounded_String;
Attendance_Input : Unbounded_String := Null_Unbounded_String;
Vegitarian_Input : Unbounded_String := Null_Unbounded_String;
Kids_Input : Natural := 0;
Temp_Attendance : Boolean := False;
Temp_Vegi : Boolean := False;
Procedure Populate_Hash_Map is
begin
Tracked_Guests.Insert(Key => To_Unbounded_String("John Smith"),
New_Item => (To_Unbounded_String("John"), True, False, 1));
Tracked_Guests.Insert(Key => To_Unbounded_String("Robert Johnson"),
New_Item => (To_Unbounded_String("Rob"), True, True, 2));
end Populate_Hash_Map;
procedure Print_Hash_Map(Position : Guest_Tracker.Cursor) is ---
begin
Put_Line("The key: " & To_String(Guest_Tracker.Key(Position)) &
" the data item: ");
Put(Guest_Tracker.Element(Position)); ----THIS IS WHERE ERRORS OCCUR
end Print_Hash_Map; ----
begin
Populate_Hash_Map;
loop
Put_Line(" - Menu - ");
Put_Line(" - 1 - Enter new value.");
Put_Line(" - 2 - Delete Existing Value.");
Put_Line(" - 3 - Print entire hashmap.");
Put_Line(" - 4 - Exit Application.");
New_Line;
Put(" - > ");
declare
begin
Get(User_Input);
exception
when Data_Error =>
Put_Line("ERROR : The entered value is not an integer, please try again!");
User_Input := 0;
when others =>
Put_Line("ERROR: An unknown error has occured!");
end;
Skip_Line;
New_Line;
if User_Input = 1 then
Put_Line("Enter a new value.");
Put(" Name - > ");
Name_Input := Get_Line;
Name := Name_Input;
New_Line;
Put(" Attending? (yes/y/no/n) - > ");
Attendance_Input := Get_Line;
New_Line;
Put(" Vegitarian? (yes/y/no/n) - > ");
Vegitarian_Input := Get_Line;
New_Line;
Put("How many children attending? - > ");
Get(Kids_Input);
New_Line;
if (U_To_L(Attendance_Input) = To_Unbounded_String("no"))
or (U_To_L(Attendance_Input) = To_Unbounded_String("n")) then
Temp_Attendance := False;
elsif (U_To_L(Attendance_Input) = To_Unbounded_String("y"))
or (U_To_L(Attendance_Input) = To_Unbounded_String("yes")) then
Temp_attendance := True;
else
Put_Line("WARNING: The confirmation that you entered is not recognized.");
end if;
if (U_To_L(Vegitarian_Input) = To_Unbounded_String("no"))
or (U_To_L(Vegitarian_Input) = To_Unbounded_String("n")) then
Temp_Vegi := False;
elsif (U_To_L(Vegitarian_Input) = To_Unbounded_String("y"))
or (U_To_L(Vegitarian_Input) = To_Unbounded_String("yes")) then
Temp_Vegi := True;
else
Put_Line("WARNING: The confirmation that you entered is not recognized.");
end if;
Guest_Tracker.Insert(Container => Tracked_Guests,
Key => Name_Input,
New_item => (Name, Temp_Attendance, Temp_Vegi, Kids_Input));
elsif User_Input = 2 then
Put("Delete a value - > ");
Name_Input := Get_Line;
New_Line;
declare
begin
Guest_Tracker.Delete(Container => Tracked_Guests,
Key => Name_Input);
exception
when Constraint_Error =>
Put_Line("The name: '" & To_String(Name_Input) & "' is not found.");
when others =>
Put_Line("ERROR: Another error has been discovered!");
end;
elsif User_Input = 3 then
Tracked_Guests.Iterate(Print_Hash_Map'access);
New_Line;
elsif User_Input = 4 then
exit;
end if;
end loop;
end Hashmap_Records;
This has nothing to do with the hash map. You are assuming that there is a Put procedure that outputs your record type Guest but there is none. You only have the Put subroutines from Ada.Text_IO, Ada.Integer_Text_IO and Ada.Text_IO.Unbounded_IO available, none of which take a value of type Guest as parameter.
Ada does not automatically generate a subroutine to pretty-print a record value when you define the record type. You have to do it yourself, e.g.
procedure Put (Value : Guest) is
begin
Put ("Guest(Name: "); -- supplied by Ada.Text_IO
Put (Value.Name); -- supplied by Ada.Text_IO.Unbounded_IO
Put (", Attending: ");
Put (Value.Attending'Img); -- 'Img is GNAT-specific; standard is
-- Boolean'Image (Value.Attending)
Put (", Vegitarian: ");
Put (Value.Vegitarian'Img);
Put (", Kids: ");
Put (Value.Kids); -- supplied by Ada.Integer_Text_IO
Put (")");
end Put;

Multi Searching string in a txt file and count

$^F8::
File := "*.txt" ;can include directory -- * is wildcard
FileHit := "" ;for reading .txt file
ArrayName:= []
ArrayName[1]:= "CAM_NWID=ESH050RN"
ArrayName[2]:= "CAM_NWID=ESH012RW"
ArrayName[3]:= "CAM_NWID=ESH016RN"
ArrayName[4]:= "CAM_NWID=ESH027RN"
ArrayValue:=[]
ArrayStore:=[]
ArrayValue[j] := A_LoopField
ArrayValue[j, k] := A_LoopReadLine
ArrayCount := 0
InputBox, Directory, Enter the searched directory, Leave empty to use the current directory. Subfolders will
be also searched., , 300, 150
if ErrorLevel
{
MsgBox, Query canceled.
Exit
}
Directory := RegExReplace(Directory, "\\$") ; Removes the leading backslash, if present.
If Directory
{
If(!InStr(FileExist(Directory), "D"))
{
msgbox Invalid directory
Exit
}
StringRight, DirectoryEndingChar, Directory, 1
If(DirectoryEndingChar != "\")
Directory .= "\"
}
Loop, %Directory%%File%, , 1
{
FileRead, FileCheck, %A_LoopFileLongPath%
IfInString, FileCheck, %StringCheck%
FileHit%A_Index% := A_LoopFileLongPath
}
Loop,%Directory%%File%, *.txt, FR
{
for key, val in ArrayName
Loop, Read, %A_LoopFileFullPath%, objects.txt
{
IfInString, A_LoopReadLine, % val
{
StringReplace, CurrentLine, A_LoopReadLine, Name(wstring:64)=
FileAppend, %CurrentLine%`n
vText := CurrentLine
StrReplace(vText, CurrentLine, "", vCount)
;;MsgBox, % CurrentLine" = " .vCount
If (CurrentLine = CurrentLine)
{
ArrayValue[key,k]:=ArrayValue.Insert(key,A_LoopReadLine) ;;this should be the the point where I can pass the value to another specific index in other array
}
ArrayCount+=1
ArrayValue[ArrayCount]
}
}
Loop % ArrayCount
{
element := ArrayValue[A_Index]
MsgBox % "Element Number " . A_Index . " is " . ArrayValue[A_Index]
}
}
return
I was not able to pass the value into the other array(ArrayValue) so
that i can keep track of the counts
MY Goal: Read folder with all text file
Search sets of Array inside the text file (which is the strings)
Everytime it matches should add +1 to the counter
Then Print in order the results in notepad
ex.
CAM_NWID=050RN = 10
CAM_NWID=012RW = 2
CAM_NWID=016RN = 6
CAM_NWID=027RN = 1

Is there a better way to insert "|' into binary string rep to get this 10|000|001

Is there a better way to insert "|" into a string
given a binary string representation of decimal 200 = 11001000
this function returns a string = 11|001|000
While this function works, it seems very kludgy!! Why is it so
hard in GO to do a simple character insertion???
func (i Binary) FString() string {
a := strconv.FormatUint(i.Get(), 2)
y := make([]string, len(a), len(a)*2)
data := []rune(a)
r := []rune{}
for i := len(data) - 1; i >= 0; i-- {
r = append(r, data[i])
}
for j := len(a) - 1; j >= 0; j-- {
y = append(y, string(r[j]))
if ((j)%3) == 0 && j > 0 {
y = append(y, "|")
}
}
return strings.Join(y, "")
}
Depends on what you call better. I'd use regular expressions.
In this case, the complexity arises from inserting separators from the right. If we padded the string so that its length was a multiple of 3, we could insert the separator from the left. And we could easily use a regular expression to insert | before every three characters. Then, we can just strip off the leading | + padding.
func (i Binary) FString() string {
a := strconv.FormatUint(i.Get(), 2)
pad_req := len(a) % 3
padding := strings.Repeat("0", (3 - pad_req))
a = padding + a
re := regexp.MustCompile("([01]{3})")
a = re.ReplaceAllString(a, "|$1")
start := len(padding) + 1
if len(padding) == 3 {
// If we padded with "000", we want to remove the `|` before *and* after it
start = 5
}
a = a[start:]
return a
}
Snippet on the Go Playground
If performance is not critical and you just want a compact version, you may copy the input digits to output, and insert a | symbol whenever a group of 2 has been written to the output.
Groups are counted from right-to-left, so when copying the digits from left-to-right, the first group might be smaller. So the counter of digits inside a group may not necessarily start from 0 in case of the first group, but from len(input)%3.
Here is an example of it:
func Format(s string) string {
b, count := &bytes.Buffer{}, len(s)%3
for i, r := range s {
if i > 0 && count == i%3 {
b.WriteRune('|')
}
b.WriteRune(r)
}
return b.String()
}
Testing it:
for i := uint64(0); i < 10; i++ {
fmt.Println(Format(strconv.FormatUint(i, 2)))
}
fmt.Println(Format(strconv.FormatInt(1234, 2)))
Output (try it on the Go Playground):
0
1
10
11
100
101
110
111
1|000
1|001
10|011|010|010
If you have to do this many times and performance does matter, then check out my answer to the question: How to fmt.Printf an integer with thousands comma
Based on that a fast solution can be:
func Format(s string) string {
out := make([]byte, len(s)+(len(s)-1)/3)
for i, j, k := len(s)-1, len(out)-1, 0; ; i, j = i-1, j-1 {
out[j] = s[i]
if i == 0 {
return string(out)
}
if k++; k == 3 {
j, k = j-1, 0
out[j] = '|'
}
}
}
Output is the same of course. Try it on the Go Playground.
This is a partitioning problem. You can use this function:
func partition(s, separator string, pLen int) string {
if pLen < 1 || len(s) == 0 || len(separator) == 0 {
return s
}
buffer := []rune(s)
L := len(buffer)
pCount := L / pLen
result := []string{}
index := 0
for ; index < pCount; index++ {
_from := L - (index+1)*pLen
_to := L - index*pLen
result = append(result, string(buffer[_from:_to]))
}
if L%pLen != 0 {
result = append(result, string(buffer[0:L-index*pLen]))
}
for h, t := 0, len(result)-1; h < t; h, t = h+1, t-1 {
result[t], result[h] = result[h], result[t]
}
return strings.Join(result, separator)
}
And s := partition("11001000", "|", 3) will give you 11|001|000.
Here is a little test:
func TestSmokeTest(t *testing.T) {
input := "11001000"
s := partition(input, "|", 3)
if s != "11|001|000" {
t.Fail()
}
s = partition(input, "|", 2)
if s != "11|00|10|00" {
t.Fail()
}
input = "0111001000"
s = partition(input, "|", 3)
if s != "0|111|001|000" {
t.Fail()
}
s = partition(input, "|", 2)
if s != "01|11|00|10|00" {
t.Fail()
}
}

Finding data in large binary file and output with context

Prologue / Context
Last week my root filesystem was remounted readonly serveral times and I took a complete snapshot via ddrescue. Sadly the filesystem was damaged already and some files are missing. At the moment I try to find my ejabberd user-database which should be somewhere within the image. Testdisk found the required file (marked as deleted) but could not restore it. Since the file is pretty small and I have a backup from some month ago I thought about doing a binary search over the whole image.
So now I have a 64GB file with a damaged filesystem and would like to extract some 4kb blocks which contain a certain pattern.
Question
How can I find the data within the 64GB large file and extract the result with some context (4kb)?
Since the filesystem image resides on my server I would prefer a linux cli tool.
The Tool
Since I couldn't find a tool which meet my requirements I wrote it myself in golang. I call it bima (for binary match). It isn't pretty but it did the job:
package main
import (
"bytes"
"encoding/hex"
"fmt"
"gopkg.in/alecthomas/kingpin.v1"
"io"
"log"
"math"
"os"
)
var (
debug = kingpin.Flag("debug", "Enable debug mode.").Short('d').Bool()
bsize = kingpin.Flag("blocksize", "Blocksize").Short('b').Default("126976").Int()
debugDetail = kingpin.Flag("debugdetail", "Debug Detail").Short('v').Default("10").Int()
matchCommand = kingpin.Command("match", "Match a value")
matchCommandValue = matchCommand.Arg("value", "The value (Hex Encoded e.g.: 616263 == abc)").Required().String()
matchCommandFile = matchCommand.Arg("file", "The file").Required().String()
)
func main() {
kingpin.Version("0.1")
mode := kingpin.Parse()
if *bsize <= 0 {
log.Fatal("The blocksize has to be larger than 0")
}
if *debugDetail <= 0 {
log.Fatal("The Debug Detail has to be larger than 0")
}
if mode == "match" {
searchBytes, err := hex.DecodeString(*matchCommandValue)
if err != nil {
log.Fatal(err)
}
scanFile(searchBytes, *matchCommandFile)
}
}
func scanFile(search []byte, path string) {
searchLength := len(search)
blocksize := *bsize
f, err := os.Open(path)
if err != nil {
log.Fatal(err)
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
log.Fatal(err)
}
filesize := fi.Size()
expectedRounds := int(math.Ceil(float64(filesize-int64(searchLength))/float64(blocksize)) + 1)
if expectedRounds <= 0 {
expectedRounds = 1
}
data := make([]byte, 0, blocksize+searchLength-1)
data2 := make([]byte, 0, blocksize+searchLength-1)
offset := make([]byte, searchLength-1)
//reading the len of the slice or less (but not the cap)
readCount, err := f.Read(offset)
if err == io.EOF {
fmt.Println("The files seems to be empty")
return
} else if err != nil {
log.Fatal(err)
}
data = append(data, offset...)
buffer := make([]byte, blocksize)
var blockpos int
var idx int
blockpos = 0
lastLevel := -1
roundLevel := 0
idxOffset := 0
for round := 0; ; round++ {
if *debug {
roundLevel = ((round * 100) / expectedRounds)
if (roundLevel%*debugDetail == 0) && (roundLevel > lastLevel) {
lastLevel = roundLevel
fmt.Fprintln(os.Stderr, "Starting round", round+1, "of", expectedRounds, "--", ((round * 100) / expectedRounds))
}
}
//At EOF, the count will be zero and err will be io.EOF
readCount, err = f.Read(buffer)
if err != nil {
if err == io.EOF {
if *debug {
fmt.Fprintln(os.Stderr, "Done - Found EOF")
}
break
}
fmt.Println(err)
return
}
data = append(data, buffer[:readCount]...)
data2 = data
idxOffset = 0
for {
idx = bytes.Index(data2, search)
if idx >= 0 {
fmt.Println(blockpos + idxOffset + idx)
if idx+searchLength < len(data2) {
data2 = data2[idx+searchLength:]
idxOffset += idx
} else {
break
}
} else {
break
}
}
data = data[readCount:]
blockpos += readCount
}
}
The Story
For completeness here comes what I did to solve my problem:
At first I used hexedit to find out, that all db files have the same header. Encoded in hex it looks like this: 0102030463584d0b0000004b62574c41
So I used my tool to find all occurrences within my sda.image file:
./bima match 0102030463584d0b0000004b62574c41 ./sda.image >DBfiles.txt
For the 64GB this took about 8 Minutes and I think the HDD was the limiting factor.
The result where about 1200 occurrences which I extracted from the image with dd. As I didn't know the exact size of the files I simply extracted chunks of 20.000 bytes:
for f in $(cat DBfiles.txt); do
dd if=sda.image of=$f.dunno bs=1 ibs=1 skip=$f count=20000
done
Now I had about 1200 files and had to find the right ones. In a first step I search for the passwd files (passwd.DCD and passwd.DCL). later I did the same for the roster files. As the header of the files contains the name, I simply greped for passwd:
for f in *.dunno; do
if [ "$(cat $f | head -c 200 | grep "passwd" | wc -l)" == "1" ]; then
echo "$f" | sed 's/\.$//g' >> passwd_files.list
fi
done
Because the chunks were larger than the files I had to find the end of each files manually. I did the corrections with Curses Hexedit.
During that process I could see that the head of each file contained either dcl_logk or dcd_logk. So I knew which of the files were DCL files and which were DCD files.
In the end I had each file up to ten times and had to decide which version I wanted to use. In general I took the largest file. After putting the files in the DB directory of the new ejabberd server and restarting it, all accounts are back again. :-)

Resources